File: FileChangesMonitor.cs
Project: ndp\fx\src\xsp\system\Web\System.Web.csproj (System.Web)
//------------------------------------------------------------------------------
// <copyright file="FileChangesMonitor.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------
 
namespace System.Web {
    using System.Collections;
    using System.Collections.Specialized;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.IO;
    using System.Runtime.InteropServices;
    using System.Security;
    using System.Security.Permissions;
    using System.Text;
    using System.Threading;
    using System.Web.Configuration;
    using System.Web.Hosting;
    using System.Web.Util;
    using Microsoft.Win32;
 
 
    // Type of the callback to the subscriber of a file change event in FileChangesMonitor.StartMonitoringFile
    delegate void FileChangeEventHandler(Object sender, FileChangeEvent e); 
 
    // The type of file change that occurred.
    enum FileAction {
        Dispose = -2,
        Error = -1,
        Overwhelming = 0,
        Added = 1,
        Removed = 2,
        Modified = 3,
        RenamedOldName = 4,
        RenamedNewName = 5
    }
 
    // Event data for a file change notification
    sealed class FileChangeEvent : EventArgs {
        internal FileAction   Action;       // the action
        internal string       FileName;     // the file that caused the action
 
        internal FileChangeEvent(FileAction action, string fileName) {
            this.Action = action;
            this.FileName = fileName;
        }
    }
 
    // Contains information about the target of a file change notification
    sealed class FileMonitorTarget {
        internal readonly FileChangeEventHandler    Callback;   // the callback
        internal readonly string                    Alias;      // the filename used to name the file
        internal readonly DateTime                  UtcStartMonitoring;// time we started monitoring
        int                                         _refs;      // number of uses of callbacks
 
        internal FileMonitorTarget(FileChangeEventHandler callback, string alias) {
            Callback = callback;
            Alias = alias;
            UtcStartMonitoring = DateTime.UtcNow;
            _refs = 1;
        }
 
        internal int AddRef() {
            _refs++;
            return _refs;
        }
 
        internal int Release() {
            _refs--;
            return _refs;
        }
 
#if DBG
        internal string DebugDescription(string indent) {
            StringBuilder   sb = new StringBuilder(200);
            string          i2 = indent + "    ";
 
            sb.Append(indent + "FileMonitorTarget\n");
            sb.Append(i2 + "       Callback: " + Callback.Target + "(HC=" + Callback.Target.GetHashCode().ToString("x", NumberFormatInfo.InvariantInfo) + ")\n");
            sb.Append(i2 + "          Alias: " + Alias + "\n");
            sb.Append(i2 + "StartMonitoring: " + Debug.FormatUtcDate(UtcStartMonitoring) + "\n");
            sb.Append(i2 + "          _refs: " + _refs + "\n");
 
            return sb.ToString();
        }
#endif
    }
 
#if !FEATURE_PAL // FEATURE_PAL does not enable access control
    sealed class FileSecurity {
        const int DACL_INFORMATION = 
                UnsafeNativeMethods.DACL_SECURITY_INFORMATION |
                UnsafeNativeMethods.GROUP_SECURITY_INFORMATION |
                UnsafeNativeMethods.OWNER_SECURITY_INFORMATION;
 
        static Hashtable    s_interned;
        static byte[]       s_nullDacl;
 
        class DaclComparer : IEqualityComparer {
            // Compares two objects. An implementation of this method must return a
            // value less than zero if x is less than y, zero if x is equal to y, or a
            // value greater than zero if x is greater than y.
            //
 
            private int Compare(byte[] a, byte[] b) {
                int result = a.Length - b.Length;
                for (int i = 0; result == 0 && i < a.Length ; i++) {
                    result = a[i] - b[i];
                }
 
                return result;
            }
 
            bool IEqualityComparer.Equals(Object x, Object y) {
                if (x == null && y == null) {
                    return true;
                }
 
                if (x == null || y == null) {
                    return false;
                }
 
                byte[] a = x as byte[];
                byte[] b = y as byte[];
                
                if (a == null || b == null) {
                    return false;
                }
 
                return Compare(a, b) == 0;
            }
 
            int IEqualityComparer.GetHashCode(Object obj) {
                byte[] a = (byte[]) obj;
 
                HashCodeCombiner combiner = new HashCodeCombiner();
                foreach (byte b in a) {
                    combiner.AddObject(b);
                }
 
                return combiner.CombinedHash32;
            }
        }
 
        static FileSecurity() {
            s_interned = new Hashtable(0, 1.0f, new DaclComparer());
            s_nullDacl = new byte[0];
         }
 
        [SuppressMessage("Microsoft.Interoperability", "CA1404:CallGetLastErrorImmediatelyAfterPInvoke",
                         Justification="Microsoft: Call to GetLastWin32Error() does follow P/Invoke call that is outside the if/else block.")]
        static internal byte[] GetDacl(string filename) {
            // DevDiv #322858 - allow skipping DACL step for perf gain
            if (HostingEnvironment.FcnSkipReadAndCacheDacls) {
                return s_nullDacl;
            }
 
            // DevDiv #246973
            // Perf: Start with initial buffer size to minimize File IO
            //
            int lengthNeeded = 512;
            byte[] dacl = new byte[lengthNeeded];
            int fOK = UnsafeNativeMethods.GetFileSecurity(filename, DACL_INFORMATION, dacl, dacl.Length, ref lengthNeeded);
 
            if (fOK != 0) {
                // If no size is needed, return a non-null marker
                if (lengthNeeded == 0) {
                   Debug.Trace("GetDacl", "Returning null dacl");
                   return s_nullDacl;
                }
                
                // Shrink the buffer to fit the whole data
                Array.Resize(ref dacl, lengthNeeded);
            }
            else {
                int hr = HttpException.HResultFromLastError(Marshal.GetLastWin32Error());
                
                // Check if need to redo the call with larger buffer
                if (hr != HResults.E_INSUFFICIENT_BUFFER) {
                    Debug.Trace("GetDacl", "Error in first call to GetFileSecurity: 0x" + hr.ToString("x", NumberFormatInfo.InvariantInfo));
                    return null;
                }
 
                // The buffer wasn't large enough.  Try again
                dacl = new byte[lengthNeeded];
                fOK = UnsafeNativeMethods.GetFileSecurity(filename, DACL_INFORMATION, dacl, dacl.Length, ref lengthNeeded);
                if (fOK == 0) {
#if DBG
                    hr = HttpException.HResultFromLastError(Marshal.GetLastWin32Error());
                    Debug.Trace("GetDacl", "Error in second to GetFileSecurity: 0x" + hr.ToString("x", NumberFormatInfo.InvariantInfo));
#endif
 
                    return null;
                }
            }
 
            byte[] interned = (byte[]) s_interned[dacl];
            if (interned == null) {
                lock (s_interned.SyncRoot) {
                    interned = (byte[]) s_interned[dacl];
                    if (interned == null) {
                        Debug.Trace("GetDacl", "Interning new dacl, length " + dacl.Length);
                        interned = dacl;
                        s_interned[interned] = interned;
                    }
                }
            }
 
            Debug.Trace("GetDacl", "Returning dacl, length " + dacl.Length);
            return interned;
        }
    }
 
    // holds information about a single file and the targets of change notification
    sealed class FileMonitor {
        internal readonly DirectoryMonitor  DirectoryMonitor;   // the parent
        internal readonly HybridDictionary  Aliases;            // aliases for this file
        string                              _fileNameLong;      // long file name - if null, represents any file in this directory
        string                              _fileNameShort;     // short file name, may be null
        HybridDictionary                    _targets;           // targets of notification
        bool                                _exists;            // does the file exist?
        FileAttributesData                  _fad;               // file attributes
        byte[]                              _dacl;              // dacl
        FileAction                          _lastAction;        // last action that ocurred on this file
        DateTime                            _utcLastCompletion; // date of the last RDCW completion
 
        internal FileMonitor(
                DirectoryMonitor dirMon, string fileNameLong, string fileNameShort, 
                bool exists, FileAttributesData fad, byte[] dacl) {
 
            DirectoryMonitor = dirMon;
            _fileNameLong = fileNameLong;
            _fileNameShort = fileNameShort;
            _exists = exists;
            _fad = fad;
            _dacl = dacl;
            _targets = new HybridDictionary();
            Aliases = new HybridDictionary(true);
        }
 
        internal string FileNameLong    {get {return _fileNameLong;}}
        internal string FileNameShort   {get {return _fileNameShort;}}
        internal bool   Exists          {get {return _exists;}}
        internal bool   IsDirectory     {get {return (FileNameLong == null);}}
        internal FileAction LastAction {
            get {return _lastAction;}
            set {_lastAction = value;}
        }
 
        internal DateTime UtcLastCompletion {
            get {return _utcLastCompletion;}
            set {_utcLastCompletion = value;}
        }
 
        // Returns the attributes of a file, updating them if the file has changed.
        internal FileAttributesData Attributes {
            get {return _fad;}
        }
 
        internal byte[] Dacl {
            get {return _dacl;}
        }
 
        internal void ResetCachedAttributes() {
            _fad = null;
            _dacl = null;
        }
 
        internal void UpdateCachedAttributes() {
            string path = Path.Combine(DirectoryMonitor.Directory, FileNameLong);
            FileAttributesData.GetFileAttributes(path, out _fad);
            _dacl = FileSecurity.GetDacl(path);
        }
 
        // Set new file information when a file comes into existence
        internal void MakeExist(FindFileData ffd, byte[] dacl) {
            _fileNameLong = ffd.FileNameLong;
            _fileNameShort = ffd.FileNameShort;
            _fad = ffd.FileAttributesData;
            _dacl = dacl;
            _exists = true;
        }
 
        // Remove a file from existence
        internal void MakeExtinct() {
            _fad = null;
            _dacl = null;
            _exists = false;
        }
 
        internal void RemoveFileNameShort() {
            _fileNameShort = null;
        }
 
        internal ICollection Targets {
            get {return _targets.Values;}
        }
 
         // Add delegate for this file.
        internal void AddTarget(FileChangeEventHandler callback, string alias, bool newAlias) {
            FileMonitorTarget target = (FileMonitorTarget)_targets[callback.Target];
            if (target != null) {
                target.AddRef();
            }
            else {
#if DBG
                // Needs the lock to sync with DebugDescription
                lock (_targets) {
#endif                
                    _targets.Add(callback.Target, new FileMonitorTarget(callback, alias));
#if DBG
                }
#endif
            }
 
            if (newAlias) {
                Aliases[alias] = alias;
            }
        }
 
        
        // Remove delegate for this file given the target object.
        internal int RemoveTarget(object callbackTarget) {
            FileMonitorTarget target = (FileMonitorTarget)_targets[callbackTarget];
#if DBG            
            if (FileChangesMonitor.s_enableRemoveTargetAssert) {
                Debug.Assert(target != null, "removing file monitor target that was never added or already been removed");
            }
#endif
            if (target != null && target.Release() == 0) {
#if DBG
                // Needs the lock to sync with DebugDescription
                lock (_targets) {
#endif                
                    _targets.Remove(callbackTarget);
#if DBG
                }
#endif
            }
 
            return _targets.Count;
        }
 
#if DBG
        internal string DebugDescription(string indent) {
            StringBuilder   sb = new StringBuilder(200);
            string          i2 = indent + "    ";
            string          i3 = i2 + "    ";
            DictionaryEntryTypeComparer detcomparer = new DictionaryEntryTypeComparer();
 
            sb.Append(indent + "System.Web.FileMonitor: ");
            if (FileNameLong != null) {
                sb.Append(FileNameLong);
                if (FileNameShort != null) {
                    sb.Append("; ShortFileName=" + FileNameShort);
                }
 
                sb.Append("; FileExists="); sb.Append(_exists);                
            }
            else {
                sb.Append("<ANY>");
            }
            sb.Append("\n");
            sb.Append(i2 + "LastAction="); sb.Append(_lastAction);
            sb.Append("; LastCompletion="); sb.Append(Debug.FormatUtcDate(_utcLastCompletion));
            sb.Append("\n");
 
            if (_fad != null) {
                sb.Append(_fad.DebugDescription(i2));
            }
            else {
                sb.Append(i2 + "FileAttributesData = <null>\n");
            }
 
            DictionaryEntry[] delegateEntries;
 
            lock (_targets) {
                sb.Append(i2 + _targets.Count + " delegates...\n");
 
                delegateEntries = new DictionaryEntry[_targets.Count];
                _targets.CopyTo(delegateEntries, 0);
            }
            
            Array.Sort(delegateEntries, detcomparer);
            
            foreach (DictionaryEntry d in delegateEntries) {
                sb.Append(i3 + "Delegate " + d.Key.GetType() + "(HC=" + d.Key.GetHashCode().ToString("x", NumberFormatInfo.InvariantInfo) + ")\n");
            }
 
            return sb.ToString();
        }
#endif
 
    }
 
    // Change notifications delegate from native code.
    delegate void NativeFileChangeNotification(FileAction action, [In, MarshalAs(UnmanagedType.LPWStr)] string fileName, long ticks);
 
    // 
    // Wraps N/Direct calls to native code that does completion port
    // based ReadDirectoryChangesW().
    // This needs to be a separate object so that a DirectoryMonitory
    // can start monitoring while the old _rootCallback has not been
    // disposed.
    //
    sealed class DirMonCompletion : IDisposable {
        static int _activeDirMonCompletions = 0;            // private counter used via reflection by FCN check-in suite
 
        DirectoryMonitor    _dirMon;                        // directory monitor
        IntPtr              _ndirMonCompletionPtr;          // pointer to native dir mon as int (used only to construct HandleRef)
        HandleRef           _ndirMonCompletionHandle;       // handleref of a pointer to native dir mon as int
        GCHandle            _rootCallback;                  // roots this callback to prevent collection
        int                 _disposed;                      // set to 1 when we call DirMonClose
        object              _ndirMonCompletionHandleLock;
 
        internal static int ActiveDirMonCompletions { get { return _activeDirMonCompletions; } }
 
        internal DirMonCompletion(DirectoryMonitor dirMon, string dir, bool watchSubtree, uint notifyFilter) {
            Debug.Trace("FileChangesMonitor", "DirMonCompletion::ctor " + dir + " " + watchSubtree.ToString() + " " + notifyFilter.ToString(NumberFormatInfo.InvariantInfo));
 
            int                             hr;
            NativeFileChangeNotification    myCallback;
 
            _dirMon = dirMon;
            myCallback = new NativeFileChangeNotification(this.OnFileChange);
            _ndirMonCompletionHandleLock = new object();
            try {
            }
            finally {
                // protected from ThreadAbortEx
                lock(_ndirMonCompletionHandleLock) {
                    // Dev10 927846: The managed DirMonCompletion.ctor calls DirMonOpen to create and initialize the native DirMonCompletion.
                    // If this succeeds, the managed DirMonCompletion.ctor creates a GCHandle to root itself so the target of the callback
                    // stays alive.  When the native DirMonCompletion is freed it invokes the managed callback with ACTION_DISPOSE to
                    // release the GCHandle.  In order for the native DirMonCOmpletion to be freed, either DirMonOpen must fail or
                    // the managed DirMonCompletion.Dispose must be called and it must invoke DirMonClose.  Waiting until the native
                    // DirMonCompletion.dtor is called to release the GCHandle ensures that the directory handle has been closed,
                    // the i/o completions have finished and there are no other threads calling the managed callback.  This is because
                    // we AddRef when we initiate i/o and we Release when the i/o completion finishes.
                    
                    // If I don't do this, myCallback will be collected by GC since its only reference is
                    // from the native code.
                    _rootCallback = GCHandle.Alloc(myCallback);
 
                    hr = UnsafeNativeMethods.DirMonOpen(dir, HttpRuntime.AppDomainAppId, watchSubtree, notifyFilter, dirMon.FcnMode, myCallback, out _ndirMonCompletionPtr);
                    if (hr != HResults.S_OK) {
                        _rootCallback.Free();
                        throw FileChangesMonitor.CreateFileMonitoringException(hr, dir);
                    }
                    
                    _ndirMonCompletionHandle = new HandleRef(this, _ndirMonCompletionPtr);
                    Interlocked.Increment(ref _activeDirMonCompletions);
                }
            }
        }
 
        ~DirMonCompletion() {
            Dispose(false);
        }
 
        void IDisposable.Dispose() {
            Dispose(true);
            System.GC.SuppressFinalize(this);
        }
 
        void Dispose(bool disposing) {
            Debug.Trace("FileChangesMonitor", "DirMonCompletion::Dispose");
            // this is here because callbacks from native code can cause
            // this to be invoked from multiple threads concurrently, and
            // I don't want contention on _ndirMonCompletionHandleLock,
            // but also need to ensure that DirMonOpen returns and the
            // _ndirMonCompletionHandle is set before DirMonClose is called.
            if (Interlocked.Exchange(ref _disposed, 1) == 0) {
 
                // Dev10 927846: There is a small window during which the .ctor has
                // not returned from DirMonOpen yet but because we already started
                // monitoring, we might receive a change notification which could 
                // potentially Dispose the instance, so we need to block until 
                // DirMonOpen returns and _ndirMonCompletionHandler is set
                lock(_ndirMonCompletionHandleLock) {
                    // Dev11 - 364642: if Dispose is called while finalizing for AD unload then
                    // the native DirMonCompletion won't be able to call back into the appdomain.
                    // But it does not need to because _rootCallback is already reclaimed as part of AD unload
                    bool fNeedToSendFileActionDispose = !AppDomain.CurrentDomain.IsFinalizingForUnload();
                    HandleRef ndirMonCompletionHandle = _ndirMonCompletionHandle;
                    if (ndirMonCompletionHandle.Handle != IntPtr.Zero) {
                        _ndirMonCompletionHandle = new HandleRef(this, IntPtr.Zero);
                        UnsafeNativeMethods.DirMonClose(ndirMonCompletionHandle, fNeedToSendFileActionDispose);
                    }
                }
            }
        }
 
        void OnFileChange(FileAction action, string fileName, long ticks) {
            DateTime utcCompletion;
            if (ticks == 0) {
                utcCompletion = DateTime.MinValue;
            }
            else {
                utcCompletion = DateTimeUtil.FromFileTimeToUtc(ticks);
            }
 
#if DBG
            Debug.Trace("FileChangesMonitorOnFileChange", "Action=" + action + "; Dir=" + _dirMon.Directory + "; fileName=" +  Debug.ToStringMaybeNull(fileName) + "; completion=" + Debug.FormatUtcDate(utcCompletion) + ";_ndirMonCompletionPtr=0x" + _ndirMonCompletionPtr.ToString("x"));
#endif
 
            //
            // The native DirMonCompletion sends FileAction.Dispose
            // when there are no more outstanding calls on the 
            // delegate. Only then can _rootCallback be freed.
            //
            if (action == FileAction.Dispose) {
                if (_rootCallback.IsAllocated) {
                    _rootCallback.Free();
                }
                Interlocked.Decrement(ref _activeDirMonCompletions);
            }
            else {
                using (new ApplicationImpersonationContext()) {
                    _dirMon.OnFileChange(action, fileName, utcCompletion);
                }
            }
        }
 
#if DBG
        internal string DebugDescription(string indent) {
            int hc = ((Delegate)_rootCallback.Target).Target.GetHashCode();
            string description = indent + "_ndirMonCompletionPtr=0x" + _ndirMonCompletionPtr.ToString("x") + "; callback=0x" + hc.ToString("x", NumberFormatInfo.InvariantInfo) + "\n";
            return description;
        }
#endif
    }
 
    sealed class NotificationQueueItem {
        internal readonly FileChangeEventHandler Callback;
        internal readonly string                 Filename;
        internal readonly FileAction             Action;
 
        internal NotificationQueueItem(FileChangeEventHandler callback, FileAction action, string filename) {
            Callback = callback;
            Action = action;
            Filename = filename;
        }
    }
 
    //
    // Monitor changes in a single directory.
    //
    sealed class DirectoryMonitor : IDisposable {
 
        static Queue            s_notificationQueue = new Queue();
        static WorkItemCallback s_notificationCallback = new WorkItemCallback(FireNotifications);
        static int              s_inNotificationThread;    
        static int              s_notificationBufferSizeIncreased = 0;
 
        internal readonly string    Directory;                      // directory being monitored
        Hashtable                   _fileMons;                      // fileName -> FileMonitor
        int                         _cShortNames;                   // number of file monitors that are added with their short name
        FileMonitor                 _anyFileMon;                    // special file monitor to watch for any changes in directory
        bool                        _watchSubtree;                  // watch subtree?
        uint                        _notifyFilter;                  // the notify filter for the call to ReadDirectoryChangesW
        bool                        _ignoreSubdirChange;            // when a subdirectory is deleted or renamed, ignore the notification if we're not monitoring it
        DirMonCompletion            _dirMonCompletion;              // dirmon completion
        bool                        _isDirMonAppPathInternal;       // special dirmon that monitors all files and subdirectories beneath the vroot (enabled via FCNMode registry key)
 
        // FcnMode to pass to native code
        internal int FcnMode {
            get;
            set; 
        }
 
        // constructor for special dirmon that monitors all files and subdirectories beneath the vroot (enabled via FCNMode registry key)
        internal DirectoryMonitor(string appPathInternal, int fcnMode): this(appPathInternal, true, UnsafeNativeMethods.RDCW_FILTER_FILE_AND_DIR_CHANGES, fcnMode) {
            _isDirMonAppPathInternal = true;
        }
        
        internal DirectoryMonitor(string dir, bool watchSubtree, uint notifyFilter, int fcnMode): this(dir, watchSubtree, notifyFilter, false, fcnMode) {
        }
 
        internal DirectoryMonitor(string dir, bool watchSubtree, uint notifyFilter, bool ignoreSubdirChange, int fcnMode) {
            Directory = dir;
            _fileMons = new Hashtable(StringComparer.OrdinalIgnoreCase);
            _watchSubtree = watchSubtree;
            _notifyFilter = notifyFilter;
            _ignoreSubdirChange = ignoreSubdirChange;
            FcnMode = fcnMode;
        }
 
        void IDisposable.Dispose() {
            if (_dirMonCompletion != null) {
                ((IDisposable)_dirMonCompletion).Dispose();
                _dirMonCompletion = null;
            }
 
            //
            // Remove aliases to this object in FileChangesMonitor so that
            // it is not rooted.
            //
            if (_anyFileMon != null) {
                HttpRuntime.FileChangesMonitor.RemoveAliases(_anyFileMon);
                _anyFileMon = null;
            }
 
            foreach (DictionaryEntry e in _fileMons) {
                string key = (string) e.Key;
                FileMonitor fileMon = (FileMonitor) e.Value;
                if (fileMon.FileNameLong == key) {
                    HttpRuntime.FileChangesMonitor.RemoveAliases(fileMon);
                }
            }
 
            _fileMons.Clear();
            _cShortNames = 0;
        }
 
        internal bool IsMonitoring() {
            return GetFileMonitorsCount() > 0;
        }
 
        void StartMonitoring() {
            if (_dirMonCompletion == null) {
                _dirMonCompletion = new DirMonCompletion(this, Directory, _watchSubtree, _notifyFilter);
            }
        }
 
        internal void StopMonitoring() {
            lock (this) {
                ((IDisposable)this).Dispose();
            }    
        }
 
        FileMonitor FindFileMonitor(string file) {
            FileMonitor fileMon;
 
            if (file == null) {
                fileMon = _anyFileMon;
            }
            else {
                fileMon = (FileMonitor)_fileMons[file];
            }
 
            return fileMon;
        }
 
        FileMonitor AddFileMonitor(string file) {
            string path;
            FileMonitor fileMon;
            FindFileData ffd = null;
            int hr;
 
            if (String.IsNullOrEmpty(file)) {
                // add as the <ANY> file monitor
                fileMon = new FileMonitor(this, null, null, true, null, null);
                _anyFileMon = fileMon;
            }
            else {
                // Get the long and short name of the file
                path = Path.Combine(Directory, file);
                if (_isDirMonAppPathInternal) {
                    hr = FindFileData.FindFile(path, Directory, out ffd);
                }
                else {
                    hr = FindFileData.FindFile(path, out ffd);
                }
                if (hr == HResults.S_OK) {
                    // Unless this is FileChangesMonitor._dirMonAppPathInternal,
                    // don't monitor changes to a directory - this will not pickup changes to files in the directory.
                    if (!_isDirMonAppPathInternal
                        && (ffd.FileAttributesData.FileAttributes & FileAttributes.Directory) != 0) {
                        throw FileChangesMonitor.CreateFileMonitoringException(HResults.E_INVALIDARG, path);
                    }
 
                    byte[] dacl = FileSecurity.GetDacl(path);
                    fileMon = new FileMonitor(this, ffd.FileNameLong, ffd.FileNameShort, true, ffd.FileAttributesData, dacl);
                    _fileMons.Add(ffd.FileNameLong, fileMon);
 
                    // Update short name aliases to this file
                    UpdateFileNameShort(fileMon, null, ffd.FileNameShort);
                }
                else if (hr == HResults.E_PATHNOTFOUND || hr == HResults.E_FILENOTFOUND) {
                    // Don't allow possible short file names to be added as non-existant,
                    // because it is impossible to track them if they are indeed a short name since
                    // short file names may change.
                    
                    // FEATURE_PAL 
 
 
 
                    if (file.IndexOf('~') != -1) {
                        throw FileChangesMonitor.CreateFileMonitoringException(HResults.E_INVALIDARG, path);
                    }
 
                    // Add as non-existent file
                    fileMon = new FileMonitor(this, file, null, false, null, null);
                    _fileMons.Add(file, fileMon);
                }
                else {
                    throw FileChangesMonitor.CreateFileMonitoringException(hr, path);
                }
            }
 
            return fileMon;
        }
 
        //
        // Update short names of a file
        //
        void UpdateFileNameShort(FileMonitor fileMon, string oldFileNameShort, string newFileNameShort) {
            if (oldFileNameShort != null) {
                FileMonitor oldFileMonShort = (FileMonitor)_fileMons[oldFileNameShort];
                if (oldFileMonShort != null) {
                    // The old filemonitor no longer has this short file name.
                    // Update the monitor and _fileMons
                    if (oldFileMonShort != fileMon) {
                        oldFileMonShort.RemoveFileNameShort();
                    }
 
                    
                    _fileMons.Remove(oldFileNameShort);
                    _cShortNames--;
                }
            }
 
            if (newFileNameShort != null) {
                // Add the new short file name.
                _fileMons.Add(newFileNameShort, fileMon);
                _cShortNames++;
            }
        }
 
        void RemoveFileMonitor(FileMonitor fileMon) {
            if (fileMon == _anyFileMon) {
                _anyFileMon = null;
            }
            else {
                _fileMons.Remove(fileMon.FileNameLong);
                if (fileMon.FileNameShort != null) {
                    _fileMons.Remove(fileMon.FileNameShort);
                    _cShortNames--;
                }
            }
 
            HttpRuntime.FileChangesMonitor.RemoveAliases(fileMon);
        }
 
        int GetFileMonitorsCount() {
            int c = _fileMons.Count - _cShortNames;
            if (_anyFileMon != null) {
                c++;
            }
 
            return c;
        }
 
        // The 4.0 CAS changes made the AppDomain homogenous, so we need to assert
        // FileIOPermission.  Currently this is only exposed publicly via CacheDependency, which
        // already does a PathDiscover check for public callers.
        [FileIOPermission(SecurityAction.Assert, Unrestricted = true)]
        internal FileMonitor StartMonitoringFileWithAssert(string file, FileChangeEventHandler callback, string alias) {
            FileMonitor fileMon = null;
            bool firstFileMonAdded = false;
 
            lock (this) {
                // Find existing file monitor
                fileMon = FindFileMonitor(file);
                if (fileMon == null) {
                    // Add a new monitor
                    fileMon = AddFileMonitor(file);
                    if (GetFileMonitorsCount() == 1) {
                        firstFileMonAdded = true;
                    }
                }
 
                // Add callback to the file monitor
                fileMon.AddTarget(callback, alias, true);
 
                // Start directory monitoring when the first file gets added
                if (firstFileMonAdded) {
                    StartMonitoring();
                }
            }
 
            return fileMon;
        }
 
        //
        // Request to stop monitoring a file.
        //
        internal void StopMonitoringFile(string file, object target) {
            FileMonitor fileMon;
            int numTargets;
 
            lock (this) {
                // Find existing file monitor
                fileMon = FindFileMonitor(file);
                if (fileMon != null) {
                    numTargets = fileMon.RemoveTarget(target);
                    if (numTargets == 0) {
                        RemoveFileMonitor(fileMon);
 
                        // last target for the file monitor gone 
                        // -- remove the file monitor
                        if (GetFileMonitorsCount() == 0) {
                            ((IDisposable)this).Dispose();
                        }
                    }
                }
            }
 
#if DBG
            if (fileMon != null) {
                Debug.Dump("FileChangesMonitor", HttpRuntime.FileChangesMonitor);
            }
#endif
        }
 
 
        internal bool GetFileAttributes(string file, out FileAttributesData fad) {
            FileMonitor fileMon = null;
            fad = null;
 
            lock (this) {
                // Find existing file monitor
                fileMon = FindFileMonitor(file);
                if (fileMon != null) {
                    // Get the attributes
                    fad = fileMon.Attributes;
                    return true;
                }
            }
 
            return false;
        }
 
        //
        // Notes about file attributes:
        // 
        // CreationTime is the time a file entry is added to a directory. 
        //     If file q1 is copied to q2, q2's creation time is updated if it is new to the directory,
        //         else q2's old time is used.
        // 
        //     If a file is deleted, then added, its creation time is preserved from before the delete.
        //     
        // LastWriteTime is the time a file was last written.    
        //     If file q1 is copied to q2, q2's lastWrite time is the same as q1.
        //     Note that this implies that the LastWriteTime can be older than the LastCreationTime,
        //     and that a copy of a file can result in the LastWriteTime being earlier than
        //     its previous value.
        // 
        // LastAccessTime is the time a file was last accessed, such as opened or written to.
        //     Note that if the attributes of a file are changed, its LastAccessTime is not necessarily updated.
        //     
        // If the FileSize, CreationTime, or LastWriteTime have changed, then we know that the 
        //     file has changed in a significant way, and that the LastAccessTime will be greater than
        //     or equal to that time.
        //     
        // If the FileSize, CreationTime, or LastWriteTime have not changed, then the file's
        //     attributes may have changed without changing the LastAccessTime.
        //
        
        // Confirm that the changes occurred after we started monitoring,
        // to handle the case where:
        //
        //     1. User creates a file.
        //     2. User starts to monitor the file.
        //     3. Change notification is made of the original creation of the file.
        // 
        // Note that we can only approximate when the last change occurred by
        // examining the LastAccessTime. The LastAccessTime will change if the 
        // contents of a file (but not necessarily its attributes) change.
        // The drawback to using the LastAccessTime is that it will also be
        // updated when a file is read.
        //
        // Note that we cannot make this confirmation when only the file's attributes
        // or ACLs change, because changes to attributes and ACLs won't change the LastAccessTime.
        // 
        bool IsChangeAfterStartMonitoring(FileAttributesData fad, FileMonitorTarget target, DateTime utcCompletion) {
            // If the LastAccessTime is more than 60 seconds before we
            // started monitoring, then the change likely did not update
            // the LastAccessTime correctly.
            if (fad.UtcLastAccessTime.AddSeconds(60) < target.UtcStartMonitoring) {
#if DBG
               Debug.Trace("FileChangesMonitorIsChangeAfterStart", "LastAccessTime is more than 60 seconds before monitoring started.");
#endif
                return true;
            }
 
            // Check if the notification of the change came after
            // we started monitoring.
            if (utcCompletion > target.UtcStartMonitoring) {
#if DBG
               Debug.Trace("FileChangesMonitorIsChangeAfterStart", "Notification came after we started monitoring.");
#endif
                return true;
            }
 
            // Make sure that the LastAccessTime is valid.
            // It must be more recent than the LastWriteTime.
            if (fad.UtcLastAccessTime < fad.UtcLastWriteTime) {
#if DBG
               Debug.Trace("FileChangesMonitorIsChangeAfterStart", "UtcLastWriteTime is greater then UtcLastAccessTime.");
#endif
                return true;
            }
 
            // If the LastAccessTime occurs exactly at midnight,
            // then the system is FAT32 and LastAccessTime is unusable.
            if (fad.UtcLastAccessTime.TimeOfDay == TimeSpan.Zero) {
#if DBG
               Debug.Trace("FileChangesMonitorIsChangeAfterStart", "UtcLastAccessTime is midnight -- FAT32 likely.");
#endif
                 return true;
            }
 
            // Finally, compare LastAccessTime to the time we started monitoring.
            // If the time of the last access was before we started monitoring, then
            // we know a change did not occur to the file contents.
            if (fad.UtcLastAccessTime >= target.UtcStartMonitoring) {
#if DBG
               Debug.Trace("FileChangesMonitorIsChangeAfterStart", "UtcLastAccessTime is greater than UtcStartMonitoring.");
#endif
                return true;
            }
 
#if DBG
               Debug.Trace("FileChangesMonitorIsChangeAfterStart", "Change is before start of monitoring.  Data:\n FileAttributesData: \nUtcCreationTime: "
               + fad.UtcCreationTime + " UtcLastAccessTime: " + fad.UtcLastAccessTime + " UtcLastWriteTime: " + fad.UtcLastWriteTime + "\n FileMonitorTarget:\n UtcStartMonitoring: "
               + target.UtcStartMonitoring + "\nUtcCompletion: " + utcCompletion);
#endif
            return false;
         }
 
        // If this is a special dirmon that monitors all files and subdirectories 
        // beneath the vroot (enabled via FCNMode registry key), then
        // we need to special case how we lookup the FileMonitor.  For example, nobody has called
        // StartMonitorFile for specific files in the App_LocalResources directory,
        // so we need to see if fileName is in App_LocalResources and then get the FileMonitor for
        // the directory.
        private bool GetFileMonitorForSpecialDirectory(string fileName, ref FileMonitor fileMon) {
 
            // fileName should not be in short form (8.3 format)...it was converted to long form in
            // DirMonCompletion::ProcessOneFileNotification
            
            // first search for match within s_dirsToMonitor
            for (int i = 0; i < FileChangesMonitor.s_dirsToMonitor.Length; i++) {
                if (StringUtil.StringStartsWithIgnoreCase(fileName, FileChangesMonitor.s_dirsToMonitor[i])) {
                    fileMon = (FileMonitor)_fileMons[FileChangesMonitor.s_dirsToMonitor[i]];
                    return fileMon != null;
                }
            }
 
            // if we did not find a match in s_dirsToMonitor, look for LocalResourcesDirectoryName anywhere within fileName
            int indexStart = fileName.IndexOf(HttpRuntime.LocalResourcesDirectoryName, StringComparison.OrdinalIgnoreCase);
            if (indexStart > -1) {
                int dirNameLength = indexStart + HttpRuntime.LocalResourcesDirectoryName.Length;
 
                // fileName should either end with LocalResourcesDirectoryName or include a trailing slash and more characters
                if (fileName.Length == dirNameLength || fileName[dirNameLength] == Path.DirectorySeparatorChar) {
                    string dirName = fileName.Substring(0, dirNameLength);
                    fileMon = (FileMonitor)_fileMons[dirName];
                    return fileMon != null;
                }
            }
 
            return false;
        }
 
 
        //
        // Delegate callback from native code.
        //
        internal void OnFileChange(FileAction action, string fileName, DateTime utcCompletion) {
            //
            // Use try/catch to prevent runtime exceptions from propagating 
            // into native code.
            //
            try {
                FileMonitor             fileMon = null;
                ArrayList               targets = null;
                int                     i, n;
                FileMonitorTarget       target;
                ICollection             col;
                string                  key;
                FileAttributesData      fadOld = null;
                FileAttributesData      fadNew = null;
                byte[]                  daclOld = null;
                byte[]                  daclNew = null;
                FileAction              lastAction = FileAction.Error;
                DateTime                utcLastCompletion = DateTime.MinValue;
                bool                    isSpecialDirectoryChange = false;
 
#if DBG
                string                  reasonIgnore = string.Empty;
                string                  reasonFire = string.Empty;
#endif
 
                // We've already stopped monitoring, but a change completion was
                // posted afterwards. Ignore it.
                if (_dirMonCompletion == null) {
                    return;
                }
 
                lock (this) {
                    if (_fileMons.Count > 0) {
                        if (action == FileAction.Error || action == FileAction.Overwhelming) {
                            // Overwhelming change -- notify all file monitors
                            Debug.Assert(fileName == null, "fileName == null");
                            Debug.Assert(action != FileAction.Overwhelming, "action != FileAction.Overwhelming");
 
                            if (action == FileAction.Overwhelming) {
                                //increase file notification buffer size, but only once per app instance
                                if (Interlocked.Increment(ref s_notificationBufferSizeIncreased) == 1) {
                                    UnsafeNativeMethods.GrowFileNotificationBuffer( HttpRuntime.AppDomainAppId, _watchSubtree );
                                }
                            }
 
                            // Get targets for all files
                            targets = new ArrayList();    
                            foreach (DictionaryEntry d in _fileMons) {
                                key = (string) d.Key;
                                fileMon = (FileMonitor) d.Value;
                                if (fileMon.FileNameLong == key) {
                                    fileMon.ResetCachedAttributes();
                                    fileMon.LastAction = action;
                                    fileMon.UtcLastCompletion = utcCompletion;
                                    col = fileMon.Targets;
                                    targets.AddRange(col);
                                }
                            }
 
                            fileMon = null;
                        }
                        else {
                            Debug.Assert((int) action >= 1 && fileName != null && fileName.Length > 0,
                                        "(int) action >= 1 && fileName != null && fileName.Length > 0");
 
                            // Find the file monitor
                            fileMon = (FileMonitor)_fileMons[fileName];
 
                            if (_isDirMonAppPathInternal && fileMon == null) {
                                isSpecialDirectoryChange = GetFileMonitorForSpecialDirectory(fileName, ref fileMon);
                            }
                            
                            if (fileMon != null) {
                                // Get the targets
                                col = fileMon.Targets;
                                targets = new ArrayList(col);
 
                                fadOld = fileMon.Attributes;
                                daclOld = fileMon.Dacl;
                                lastAction = fileMon.LastAction;
                                utcLastCompletion = fileMon.UtcLastCompletion;
                                fileMon.LastAction = action;
                                fileMon.UtcLastCompletion = utcCompletion;
 
                                if (action == FileAction.Removed || action == FileAction.RenamedOldName) {
                                    // File not longer exists.
                                    fileMon.MakeExtinct();
                                }
                                else if (fileMon.Exists) {
                                    // We only need to update the attributes if this is 
                                    // a different completion, as we retreive the attributes
                                    // after the completion is received.
                                    if (utcLastCompletion != utcCompletion) {
                                        fileMon.UpdateCachedAttributes();
                                    }
                                }
                                else {
                                    // File now exists - update short name and attributes.
                                    FindFileData ffd = null;
                                    string path = Path.Combine(Directory, fileMon.FileNameLong);
                                    int hr;
                                    if (_isDirMonAppPathInternal) {
                                        hr = FindFileData.FindFile(path, Directory, out ffd);
                                    }
                                    else {
                                        hr = FindFileData.FindFile(path, out ffd);
                                    }
                                    if (hr == HResults.S_OK) {
                                        Debug.Assert(StringUtil.EqualsIgnoreCase(fileMon.FileNameLong, ffd.FileNameLong),
                                                    "StringUtil.EqualsIgnoreCase(fileMon.FileNameLong, ffd.FileNameLong)");
 
                                        string oldFileNameShort = fileMon.FileNameShort;
                                        byte[] dacl = FileSecurity.GetDacl(path);
                                        fileMon.MakeExist(ffd, dacl);
                                        UpdateFileNameShort(fileMon, oldFileNameShort, ffd.FileNameShort);
                                    }
                                }
 
                                fadNew = fileMon.Attributes;
                                daclNew = fileMon.Dacl;
                            }
                        }
                    }
 
                    // Notify the delegate waiting for any changes
                    if (_anyFileMon != null) {
                        col = _anyFileMon.Targets;
                        if (targets != null) {
                            targets.AddRange(col);
                        }
                        else {
                            targets = new ArrayList(col);
                        }
                    }
 
                    if (action == FileAction.Error) {
                        // Stop monitoring.
                        ((IDisposable)this).Dispose();
                    }
                }
 
                // Ignore Modified action for directories (VSWhidbey 295597)
                bool ignoreThisChangeNotification = false;
 
                if (!isSpecialDirectoryChange && fileName != null && action == FileAction.Modified) {
                    // check if the file is a directory (reuse attributes if already obtained)
                    FileAttributesData fad = fadNew;
 
                    if (fad == null) {
                        string path = Path.Combine(Directory, fileName);
                        FileAttributesData.GetFileAttributes(path, out fad);
                    }
 
                    if (fad != null && ((fad.FileAttributes & FileAttributes.Directory) != 0)) {
                        // ignore if directory
                        ignoreThisChangeNotification = true;
                    }
                }
 
                // Dev10 440497: Don't unload AppDomain when a folder is deleted or renamed, unless we're monitoring files in it
                if (_ignoreSubdirChange && (action == FileAction.Removed || action == FileAction.RenamedOldName) && fileName != null) {
                    string fullPath = Path.Combine(Directory, fileName);
                    if (!HttpRuntime.FileChangesMonitor.IsDirNameMonitored(fullPath, fileName)) {
#if DBG
                        Debug.Trace("FileChangesMonitorIgnoreSubdirChange", 
                                    "*** Ignoring SubDirChange " + DateTime.Now.ToString("hh:mm:ss.fff", CultureInfo.InvariantCulture) 
                                    + ": fullPath=" + fullPath + ", action=" + action.ToString());
#endif
                        ignoreThisChangeNotification = true;
                    }
#if DBG
                    else {
                        Debug.Trace("FileChangesMonitorIgnoreSubdirChange", 
                                    "*** SubDirChange " + DateTime.Now.ToString("hh:mm:ss.fff", CultureInfo.InvariantCulture) 
                                    + ": fullPath=" + fullPath + ", action=" + action.ToString());
                    }
#endif
                }
 
                // Fire the event
                if (targets != null && !ignoreThisChangeNotification) {
                    Debug.Dump("FileChangesMonitor", HttpRuntime.FileChangesMonitor);
 
                    lock (s_notificationQueue.SyncRoot) {
                        for (i = 0, n = targets.Count; i < n; i++) {
                            //
                            // Determine whether the change is significant, and if so, add it 
                            // to the notification queue.
                            //
                            // - A change is significant if action is other than Added or Modified
                            // - A change is significant if the action is Added and it occurred after
                            //   the target started monitoring.
                            // - If the action is Modified:
                            // -- A change is significant if the file contents were modified
                            //    and it occurred after the target started monitoring.
                            // -- A change is significant if the DACL changed. We cannot check if
                            //    the change was made after the target started monitoring in this case,
                            //    as the LastAccess time may not be updated.
                            //
                            target = (FileMonitorTarget)targets[i];
                            bool isSignificantChange;
                            if ((action != FileAction.Added && action != FileAction.Modified) || fadNew == null) {
 
                                // Any change other than Added or Modified is significant.
                                // If we have no attributes to examine, the change is significant.
                                isSignificantChange = true;
 
#if DBG
                                reasonFire = "(action != FileAction.Added && action != FileAction.Modified) || fadNew == null";
#endif
                            }
                            else if (action == FileAction.Added) {
                                // Added actions are significant if they occur after we started monitoring.
                                isSignificantChange = IsChangeAfterStartMonitoring(fadNew, target, utcCompletion);
 
#if DBG
                                reasonIgnore = "change occurred before started monitoring";
                                reasonFire = "file added after start of monitoring";
#endif
 
                            }
                            else {
                                Debug.Assert(action == FileAction.Modified, "action == FileAction.Modified");
                                if (utcCompletion == utcLastCompletion) {
                                    // File attributes and ACLs will not have changed if the completion is the same
                                    // as the last, since we get the attributes after all changes in the completion
                                    // have occurred. Therefore if the previous change was Modified, there
                                    // is no change that we can detect.
                                    //
                                    // Notepad fires such spurious notifications when a file is saved.
                                    // 
                                    isSignificantChange = (lastAction != FileAction.Modified);
 
#if DBG
                                    reasonIgnore = "spurious FileAction.Modified";
                                    reasonFire = "spurious completion where action != modified";
#endif
 
                                }
                                else if (fadOld == null) {
                                    // There were no attributes before this notification, 
                                    // so assume the change is significant. We cannot check for
                                    // whether the change was after the start of monitoring,
                                    // because we don't know if the content changed, or just
                                    // DACL, in which case the LastAccessTime will not be updated.
                                    isSignificantChange = true;
 
#if DBG
                                    reasonFire = "no attributes before this notification";
#endif
                                }
                                else if (daclOld == null || daclOld != daclNew) {
                                    // The change is significant if the DACL changed. 
                                    // We cannot check if the change is after the start of monitoring,
                                    // as a change in the DACL does not necessarily update the
                                    // LastAccessTime of a file.
                                    // If we cannot access the DACL, then we must assume
                                    // that it is what has changed.
                                    isSignificantChange = true;
 
#if DBG
                                    if (daclOld == null) {
                                        reasonFire = "unable to access ACL";
                                    }
                                    else {
                                        reasonFire = "ACL changed";
                                    }
#endif
 
                                }
                                else {
                                    // The file content was modified. We cannot guarantee that the
                                    // LastWriteTime or FileSize changed when the file changed, as 
                                    // copying a file preserves the LastWriteTime, and the "touch"
                                    // command can reset the LastWriteTime of many files to the same
                                    // time.
                                    //
                                    // If the file content is modified, we can determine if the file
                                    // was not changed after the start of monitoring by looking at 
                                    // the LastAccess time.
                                    isSignificantChange = IsChangeAfterStartMonitoring(fadNew, target, utcCompletion);
 
#if DBG
                                    reasonIgnore = "change occurred before started monitoring";
                                    reasonFire = "file content modified after start of monitoring";
#endif
 
                                }
                            }
 
                            if (isSignificantChange) {
#if DBG
                                Debug.Trace("FileChangesMonitorCallback", "Firing change event, reason=" + reasonFire + 
                                    "\n\tArgs: Action=" + action +     ";     Completion=" + Debug.FormatUtcDate(utcCompletion) + "; fileName=" + fileName + 
                                    "\n\t  LastAction=" + lastAction + "; LastCompletion=" + Debug.FormatUtcDate(utcLastCompletion) + 
                                    "\nfadOld=" + ((fadOld != null) ? fadOld.DebugDescription("\t") : "<null>") +
                                    "\nfileMon=" + ((fileMon != null) ? fileMon.DebugDescription("\t") : "<null>") + 
                                    "\n" + target.DebugDescription("\t"));
#endif
 
                                s_notificationQueue.Enqueue(new NotificationQueueItem(target.Callback, action, target.Alias));
                            }
#if DBG
                            else {
                                Debug.Trace("FileChangesMonitorCallback", "Ignoring change event, reason=" + reasonIgnore +
                                    "\n\tArgs: Action=" + action +     ";     Completion=" + Debug.FormatUtcDate(utcCompletion) + "; fileName=" + fileName + 
                                    "\n\t  LastAction=" + lastAction + "; LastCompletion=" + Debug.FormatUtcDate(utcLastCompletion) + 
                                    "\nfadOld=" + ((fadOld != null) ? fadOld.DebugDescription("\t") : "<null>") +
                                    "\nfileMon=" + ((fileMon != null) ? fileMon.DebugDescription("\t") : "<null>") + 
                                    "\n" + target.DebugDescription("\t"));
 
                            }
#endif
                        }
                    }
 
                    if (s_notificationQueue.Count > 0 && s_inNotificationThread == 0 && Interlocked.Exchange(ref s_inNotificationThread, 1) == 0) {
                        WorkItem.PostInternal(s_notificationCallback);
                    }
                }
            }
            catch (Exception ex) {
                Debug.Trace(Debug.TAG_INTERNAL, 
                            "Exception thrown processing file change notification" +
                            " action=" + action.ToString() +
                            " fileName" + fileName);
 
                Debug.TraceException(Debug.TAG_INTERNAL, ex);
            }
        }
 
        // Fire notifications on a separate thread from that which received the notifications,
        // so that we don't block notification collection.
        static void FireNotifications() {
            try {
                // Outer loop: test whether we need to fire notifications and grab the lock
                for (;;) {
                    // Inner loop: fire notifications until the queue is emptied
                    for (;;) {
                        // Remove an item from the queue.
                        NotificationQueueItem nqi = null;
                        lock (s_notificationQueue.SyncRoot) {
                            if (s_notificationQueue.Count > 0) {
                                nqi = (NotificationQueueItem) s_notificationQueue.Dequeue();
                            }
                        }
 
                        if (nqi == null)
                            break;
 
                        try {
                            Debug.Trace("FileChangesMonitorFireNotification", "Firing change event" + 
                                "\n\tArgs: Action=" + nqi.Action + "; fileName=" + nqi.Filename + "; Target=" + nqi.Callback.Target + "(HC=" + nqi.Callback.Target.GetHashCode().ToString("x", NumberFormatInfo.InvariantInfo) + ")");
 
                            // Call the callback
                            nqi.Callback(null, new FileChangeEvent(nqi.Action, nqi.Filename)); 
                        }
                        catch (Exception ex) {
                            Debug.Trace(Debug.TAG_INTERNAL, 
                                        "Exception thrown in file change callback" +
                                        " action=" + nqi.Action.ToString() +
                                        " fileName" + nqi.Filename);
 
                            Debug.TraceException(Debug.TAG_INTERNAL, ex);
                        }
                    }
 
                    // Release the lock
                    Interlocked.Exchange(ref s_inNotificationThread, 0);
 
                    // We need to test again to avoid ---- where a thread that receives notifications adds to the
                    // queue, but does not spawn a thread because s_inNotificationThread = 1
                    if (s_notificationQueue.Count == 0 || Interlocked.Exchange(ref s_inNotificationThread, 1) != 0)
                        break;
                }
            }
            catch {
                Interlocked.Exchange(ref s_inNotificationThread, 0);
            }
        }
 
#if DBG
        internal string DebugDescription(string indent) {
            StringBuilder   sb = new StringBuilder(200);
            string          i2 = indent + "    ";
            DictionaryEntryCaseInsensitiveComparer  decomparer = new DictionaryEntryCaseInsensitiveComparer();
            
            lock (this) {
                DictionaryEntry[] fileEntries = new DictionaryEntry[_fileMons.Count];
                _fileMons.CopyTo(fileEntries, 0);
                Array.Sort(fileEntries, decomparer);
                
                sb.Append(indent + "System.Web.DirectoryMonitor: " + Directory + "\n");
                if (_dirMonCompletion != null) {
                    sb.Append(i2 + "_dirMonCompletion " + _dirMonCompletion.DebugDescription(String.Empty));
                }
                else {
                    sb.Append(i2 + "_dirMonCompletion = <null>\n");
                }
 
                sb.Append(i2 + GetFileMonitorsCount() + " file monitors...\n");
                if (_anyFileMon != null) {
                    sb.Append(_anyFileMon.DebugDescription(i2));
                }
 
                foreach (DictionaryEntry d in fileEntries) {
                    FileMonitor fileMon = (FileMonitor)d.Value;
                    if (fileMon.FileNameShort == (string)d.Key)
                        continue;
 
                    sb.Append(fileMon.DebugDescription(i2));
                }
            }
 
            return sb.ToString();
        }
#endif
    }
#endif // !FEATURE_PAL
 
    //
    // Manager for directory monitors.                       
    // Provides file change notification services in ASP.NET 
    //
    sealed class FileChangesMonitor {
#if !FEATURE_PAL // FEATURE_PAL does not enable file change notification
        internal static string[] s_dirsToMonitor = new string[] {
            HttpRuntime.BinDirectoryName,
            HttpRuntime.ResourcesDirectoryName,
            HttpRuntime.CodeDirectoryName,
            HttpRuntime.WebRefDirectoryName,
            HttpRuntime.BrowsersDirectoryName
        };
 
        internal const int MAX_PATH = 260;
 
        #pragma warning disable 0649
        ReadWriteSpinLock       _lockDispose;                       // spinlock for coordinating dispose
        #pragma warning restore 0649
 
        bool                    _disposed;                          // have we disposed?
        Hashtable               _aliases;                           // alias -> FileMonitor
        Hashtable               _dirs;                              // dir -> DirectoryMonitor
        DirectoryMonitor        _dirMonSubdirs;                     // subdirs monitor for renames
        Hashtable               _subDirDirMons;                     // Hashtable of DirectoryMonitor used in ListenToSubdirectoryChanges
        ArrayList               _dirMonSpecialDirs;                 // top level dirs we monitor
        FileChangeEventHandler  _callbackRenameOrCriticaldirChange; // event handler for renames and bindir
        int                     _activeCallbackCount;               // number of callbacks currently executing
        DirectoryMonitor        _dirMonAppPathInternal;             // watches all files and subdirectories (at any level) beneath HttpRuntime.AppDomainAppPathInternal
        String                  _appPathInternal;                   // HttpRuntime.AppDomainAppPathInternal
        int                     _FCNMode;                           // from registry, controls how we monitor directories
 
#if DBG
        internal static bool    s_enableRemoveTargetAssert;
#endif
 
        // Dev10 927283: We were appending to HttpRuntime._shutdownMessage in DirectoryMonitor.OnFileChange when
        // we received overwhelming changes and errors, but not all overwhelming file change notifications result
        // in a shutdown.  The fix is to only append to _shutdownMessage when the domain is being shutdown.
        internal static string GenerateErrorMessage(FileAction action, String fileName = null) {
            string message = null;
            if (action == FileAction.Overwhelming) {
                message = "Overwhelming Change Notification in ";
            }
            else if (action == FileAction.Error) {
                message = "File Change Notification Error in ";
            }
            else {
                return null;
            }
            return (fileName != null) ? message + Path.GetDirectoryName(fileName) : message;
        }
 
        internal static HttpException CreateFileMonitoringException(int hr, string path) {
            string  message;
            bool    logEvent = false;
 
            switch (hr) {
                case HResults.E_FILENOTFOUND:
                case HResults.E_PATHNOTFOUND:
                    message = SR.Directory_does_not_exist_for_monitoring;
                    break;
 
                case HResults.E_ACCESSDENIED:
                    message = SR.Access_denied_for_monitoring;
                    logEvent = true;
                    break;
 
                case HResults.E_INVALIDARG:
                    message = SR.Invalid_file_name_for_monitoring;
                    break;
 
                case HResults.ERROR_TOO_MANY_CMDS:
                    message = SR.NetBios_command_limit_reached;
                    logEvent = true;
                    break;
 
                default:
                    message = SR.Failed_to_start_monitoring;
                    break;
            }
 
 
            if (logEvent) {
                // Need to raise an eventlog too.
                UnsafeNativeMethods.RaiseFileMonitoringEventlogEvent(
                    SR.GetString(message, HttpRuntime.GetSafePath(path)) + 
                    "\n\r" + 
                    SR.GetString(SR.App_Virtual_Path, HttpRuntime.AppDomainAppVirtualPath),
                    path, HttpRuntime.AppDomainAppVirtualPath, hr);
            }
            
            return new HttpException(SR.GetString(message, HttpRuntime.GetSafePath(path)), hr);
        }
 
        internal static string GetFullPath(string alias) {
            // Assert PathDiscovery before call to Path.GetFullPath
            try {
                new FileIOPermission(FileIOPermissionAccess.PathDiscovery, alias).Assert();
            }
            catch {
                throw CreateFileMonitoringException(HResults.E_INVALIDARG, alias);
            }
 
            string path = Path.GetFullPath(alias);
            path = FileUtil.RemoveTrailingDirectoryBackSlash(path);
 
            return path;
        }
 
        private bool IsBeneathAppPathInternal(string fullPathName) {
            if (_appPathInternal != null
                && fullPathName.Length > _appPathInternal.Length+1
                && fullPathName.IndexOf(_appPathInternal, StringComparison.OrdinalIgnoreCase) > -1 
                && fullPathName[_appPathInternal.Length] == Path.DirectorySeparatorChar) {
                return true;
            }
            return false;
        }
 
        private bool IsFCNDisabled { get { return _FCNMode == 1; } }
 
        internal FileChangesMonitor(FcnMode mode) {
            // Possible values for DWORD FCNMode:
            //       does not exist == default behavior (create DirectoryMonitor for each subdir)
            //              0 or >2 == default behavior (create DirectoryMonitor for each subdir)
            //                    1 == disable File Change Notifications (FCN)
            //                    2 == create 1 DirectoryMonitor for AppPathInternal and watch subtrees
            switch (mode) {
                case FcnMode.NotSet:
                    // If the mode is not set, we use the registry key's value
                    UnsafeNativeMethods.GetDirMonConfiguration(out _FCNMode);
                    break;
                case FcnMode.Disabled:
                    _FCNMode = 1;
                    break;
                case FcnMode.Single:
                    _FCNMode = 2;
                    break;
                case FcnMode.Default:
                default:
                    _FCNMode = 0;
                    break;
            }
 
            if (IsFCNDisabled) {
                return;
            }
 
            _aliases = Hashtable.Synchronized(new Hashtable(StringComparer.OrdinalIgnoreCase));
            _dirs    = new Hashtable(StringComparer.OrdinalIgnoreCase);
            _subDirDirMons = new Hashtable(StringComparer.OrdinalIgnoreCase);
 
            if (_FCNMode == 2 && HttpRuntime.AppDomainAppPathInternal != null) {
                _appPathInternal = GetFullPath(HttpRuntime.AppDomainAppPathInternal);
                _dirMonAppPathInternal = new DirectoryMonitor(_appPathInternal, _FCNMode);
            }
 
#if DBG
            if ((int)Misc.GetAspNetRegValue(null /*subKey*/, "FCMRemoveTargetAssert", 0) > 0) {
                s_enableRemoveTargetAssert = true;
            }
#endif
 
        }
 
        internal bool IsDirNameMonitored(string fullPath, string dirName) {
            // is it one of the not-so-special directories we're monitoring?
            if (_dirs.ContainsKey(fullPath)) {
                return true;
            }
            // is it one of the special directories (bin, App_Code, etc) or a subfolder?
            foreach (string specialDirName in s_dirsToMonitor) {
                if (StringUtil.StringStartsWithIgnoreCase(dirName, specialDirName)) {
                    // a special directory?
                    if (dirName.Length == specialDirName.Length) {
                        return true;
                    }
                    // a subfolder?
                    else if (dirName.Length > specialDirName.Length && dirName[specialDirName.Length] == Path.DirectorySeparatorChar) {
                        return true;
                    }
                }
            }
            // Dev10 Bug 663511: Deletes, moves, and renames of the App_LocalResources folder may be ignored
            if (dirName.IndexOf(HttpRuntime.LocalResourcesDirectoryName, StringComparison.OrdinalIgnoreCase) > -1) {
                return true;
            }
            // we're not monitoring it
            return false;
        }
 
        //
        // Find the directory monitor. If not found, maybe add it.
        // If the directory is not actively monitoring, ensure that
        // it still represents an accessible directory.
        //
        DirectoryMonitor FindDirectoryMonitor(string dir, bool addIfNotFound, bool throwOnError) {
            DirectoryMonitor dirMon;
            FileAttributesData fad = null;
            int hr;
 
            dirMon = (DirectoryMonitor)_dirs[dir];
            if (dirMon != null) {
                if (!dirMon.IsMonitoring()) {
                    hr = FileAttributesData.GetFileAttributes(dir, out fad);
                    if (hr != HResults.S_OK || (fad.FileAttributes & FileAttributes.Directory) == 0) {
                        dirMon = null;
                    }
                }
            }
 
            if (dirMon != null || !addIfNotFound) {
                return dirMon;
            }
 
            lock (_dirs.SyncRoot) {
                // Check again, this time under synchronization.
                dirMon = (DirectoryMonitor)_dirs[dir];
                if (dirMon != null) {
                    if (!dirMon.IsMonitoring()) {
                        // Fail if it's not a directory or inaccessible.
                        hr = FileAttributesData.GetFileAttributes(dir, out fad);
                        if (hr == HResults.S_OK && (fad.FileAttributes & FileAttributes.Directory) == 0) {
                            // Fail if it's not a directory.
                            hr = HResults.E_INVALIDARG;
                        }
 
                        if (hr != HResults.S_OK) {
                            // Not accessible or a dir, so stop monitoring and remove.
                            _dirs.Remove(dir);
                            dirMon.StopMonitoring();
                            if (addIfNotFound && throwOnError) {
                                throw FileChangesMonitor.CreateFileMonitoringException(hr, dir);
                            }
 
                            return null;
                        }
                    }
                }
                else if (addIfNotFound) {
                    // Fail if it's not a directory or inaccessible.
                    hr = FileAttributesData.GetFileAttributes(dir, out fad);
                    if (hr == HResults.S_OK && (fad.FileAttributes & FileAttributes.Directory) == 0) {
                        hr = HResults.E_INVALIDARG;
                    }
 
                    if (hr == HResults.S_OK) {
                        // Add a new directory monitor.
                        dirMon = new DirectoryMonitor(dir, false, UnsafeNativeMethods.RDCW_FILTER_FILE_AND_DIR_CHANGES, _FCNMode);
                        _dirs.Add(dir, dirMon);
                    }
                    else if (throwOnError) {
                        throw FileChangesMonitor.CreateFileMonitoringException(hr, dir);
                    }
                }
            }
 
            return dirMon;
        }
 
        // Remove the aliases of a file monitor.
        internal void RemoveAliases(FileMonitor fileMon) {
            if (IsFCNDisabled) {
                return;
            }
             
            foreach (DictionaryEntry entry in fileMon.Aliases) {
                if (_aliases[entry.Key] == fileMon) {
                    _aliases.Remove(entry.Key);
                }
            }
        }
 
        //
        // Request to monitor a file, which may or may not exist.
        //
        internal DateTime StartMonitoringFile(string alias, FileChangeEventHandler callback) {
            Debug.Trace("FileChangesMonitor", "StartMonitoringFile\n" + "\tArgs: File=" + alias + "; Callback=" + callback.Target + "(HC=" + callback.Target.GetHashCode().ToString("x", NumberFormatInfo.InvariantInfo) + ")");
 
            FileMonitor         fileMon;
            DirectoryMonitor    dirMon;
            string              fullPathName, dir, file;
            bool                addAlias = false;
 
            if (alias == null) {
                throw CreateFileMonitoringException(HResults.E_INVALIDARG, alias);
            }
 
            if (IsFCNDisabled) {
                fullPathName = GetFullPath(alias);
                FindFileData ffd = null;
                int hr = FindFileData.FindFile(fullPathName, out ffd);
                if (hr == HResults.S_OK) {
                    return ffd.FileAttributesData.UtcLastWriteTime;
                }
                else {
                    return DateTime.MinValue;
                }
            }
 
            using (new ApplicationImpersonationContext()) {
                _lockDispose.AcquireReaderLock();
                try{
                    // Don't start monitoring if disposed.
                    if (_disposed) {
                        return DateTime.MinValue;
                    }
 
                    fileMon = (FileMonitor)_aliases[alias];
                    if (fileMon != null) {
                        // Used the cached directory monitor and file name.
                        dirMon = fileMon.DirectoryMonitor;
                        file = fileMon.FileNameLong;
                    }
                    else {
                        addAlias = true;
 
                        if (alias.Length == 0 || !UrlPath.IsAbsolutePhysicalPath(alias)) {
                            throw CreateFileMonitoringException(HResults.E_INVALIDARG, alias);
                        }
 
                        //
                        // Get the directory and file name, and lookup 
                        // the directory monitor.
                        //
                        fullPathName = GetFullPath(alias);
                        
                        if (IsBeneathAppPathInternal(fullPathName)) {
                            dirMon = _dirMonAppPathInternal;
                            file = fullPathName.Substring(_appPathInternal.Length+1);
                        }
                        else {
                            dir = UrlPath.GetDirectoryOrRootName(fullPathName);
                            file = Path.GetFileName(fullPathName);
                            if (String.IsNullOrEmpty(file)) {
                                // not a file
                                throw CreateFileMonitoringException(HResults.E_INVALIDARG, alias);
                            }
                            dirMon = FindDirectoryMonitor(dir, true /*addIfNotFound*/, true /*throwOnError*/);
                        }
                    }
 
                    fileMon = dirMon.StartMonitoringFileWithAssert(file, callback, alias);
                    if (addAlias) {
                        _aliases[alias] = fileMon;
                    }
                }
                finally {
                    _lockDispose.ReleaseReaderLock();
                }
 
                FileAttributesData fad;
                fileMon.DirectoryMonitor.GetFileAttributes(file, out fad);
 
                Debug.Dump("FileChangesMonitor", this);
 
                if (fad != null) {
                    return fad.UtcLastWriteTime;
                }
                else {
                    return DateTime.MinValue;
                }
            }
        }
 
        //
        // Request to monitor a path, which may be file, directory, or non-existent
        // file.
        //
        internal DateTime StartMonitoringPath(string alias, FileChangeEventHandler callback, out FileAttributesData fad) {
            Debug.Trace("FileChangesMonitor", "StartMonitoringPath\n" + "\tArgs: File=" + alias + "; Callback=" + callback.Target + "(HC=" + callback.Target.GetHashCode().ToString("x", NumberFormatInfo.InvariantInfo) + ")");
 
            FileMonitor         fileMon = null;
            DirectoryMonitor    dirMon = null;
            string              fullPathName, dir, file = null;
            bool                addAlias = false;
 
            fad = null;
 
            if (alias == null) {
                throw new HttpException(SR.GetString(SR.Invalid_file_name_for_monitoring, String.Empty));
            }
            
            if (IsFCNDisabled) {
                fullPathName = GetFullPath(alias);
                FindFileData ffd = null;
                int hr = FindFileData.FindFile(fullPathName, out ffd);
                if (hr == HResults.S_OK) {
                    fad = ffd.FileAttributesData;
                    return ffd.FileAttributesData.UtcLastWriteTime;
                }
                else {
                    return DateTime.MinValue;
                }
            }
 
            using (new ApplicationImpersonationContext()) {
                _lockDispose.AcquireReaderLock();
                try{
                    if (_disposed) {
                        return DateTime.MinValue;
                    }
 
                    // do/while loop once to make breaking out easy
                    do {
                        fileMon = (FileMonitor)_aliases[alias];
                        if (fileMon != null) {
                            // Used the cached directory monitor and file name.
                            file = fileMon.FileNameLong;
                            fileMon = fileMon.DirectoryMonitor.StartMonitoringFileWithAssert(file, callback, alias);
                            continue;
                        }
 
                        addAlias = true;
 
                        if (alias.Length == 0 || !UrlPath.IsAbsolutePhysicalPath(alias)) {
                            throw new HttpException(SR.GetString(SR.Invalid_file_name_for_monitoring, HttpRuntime.GetSafePath(alias)));
                        }
 
                        fullPathName = GetFullPath(alias);
 
                        // see if the path is beneath HttpRuntime.AppDomainAppPathInternal
                        if (IsBeneathAppPathInternal(fullPathName)) {
                            dirMon = _dirMonAppPathInternal;
                            file = fullPathName.Substring(_appPathInternal.Length+1);
                            fileMon = dirMon.StartMonitoringFileWithAssert(file, callback, alias);
                            continue;
                        }
 
                        // try treating the path as a directory
                        dirMon = FindDirectoryMonitor(fullPathName, false, false);
                        if (dirMon != null) {
                            fileMon = dirMon.StartMonitoringFileWithAssert(null, callback, alias);
                            continue;
                        }
 
                        // try treaing the path as a file
                        dir = UrlPath.GetDirectoryOrRootName(fullPathName);
                        file = Path.GetFileName(fullPathName);
                        if (!String.IsNullOrEmpty(file)) {
                            dirMon = FindDirectoryMonitor(dir, false, false);
                            if (dirMon != null) {
                                // try to add it - a file is the common case,
                                // and we avoid hitting the disk twice
                                try {
                                    fileMon = dirMon.StartMonitoringFileWithAssert(file, callback, alias);
                                }
                                catch {
                                }
 
                                if (fileMon != null) {
                                    continue;
                                }
                            }
                        }
 
                        // We aren't monitoring this path or its parent directory yet. 
                        // Hit the disk to determine if it's a directory or file.
                        dirMon = FindDirectoryMonitor(fullPathName, true, false);
                        if (dirMon != null) {
                            // It's a directory, so monitor all changes in it
                            file = null;
                        }
                        else {
                            // It's not a directory, so treat as file
                            if (String.IsNullOrEmpty(file)) {
                                throw CreateFileMonitoringException(HResults.E_INVALIDARG, alias);
                            }
 
                            dirMon = FindDirectoryMonitor(dir, true, true);
                        }
 
                        fileMon = dirMon.StartMonitoringFileWithAssert(file, callback, alias);
                    } while (false);
 
                    if (!fileMon.IsDirectory) {
                        fileMon.DirectoryMonitor.GetFileAttributes(file, out fad);
                    }
 
                    if (addAlias) {
                        _aliases[alias] = fileMon;
                    }
                }
                finally {
                    _lockDispose.ReleaseReaderLock();
                }
 
                Debug.Dump("FileChangesMonitor", this);
 
                if (fad != null) {
                    return fad.UtcLastWriteTime;
                }
                else {
                    return DateTime.MinValue;
                }
            }
        }
 
        //
        // Request to monitor the bin directory and directory renames anywhere under app
        //
 
        internal void StartMonitoringDirectoryRenamesAndBinDirectory(string dir, FileChangeEventHandler callback) {
            Debug.Trace("FileChangesMonitor", "StartMonitoringDirectoryRenamesAndBinDirectory\n" + "\tArgs: File=" + dir + "; Callback=" + callback.Target + "(HC=" + callback.Target.GetHashCode().ToString("x", NumberFormatInfo.InvariantInfo) + ")");
 
            if (String.IsNullOrEmpty(dir)) {
                throw new HttpException(SR.GetString(SR.Invalid_file_name_for_monitoring, String.Empty));
            }
 
            if (IsFCNDisabled) {
                return;
            }
 
#if DBG
            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.");
#endif
            using (new ApplicationImpersonationContext()) {
                _lockDispose.AcquireReaderLock();
                try {
                    if (_disposed) {
                        return;
                    }
 
                    _callbackRenameOrCriticaldirChange = callback;
 
                    string dirRoot = GetFullPath(dir);
 
                    // Monitor bin directory and app directory (for renames only) separately
                    // to avoid overwhelming changes when the user writes to a subdirectory
                    // of the app directory.
 
                    _dirMonSubdirs = new DirectoryMonitor(dirRoot, true, UnsafeNativeMethods.RDCW_FILTER_DIR_RENAMES, true, _FCNMode);
                    try {
                        _dirMonSubdirs.StartMonitoringFileWithAssert(null, new FileChangeEventHandler(this.OnSubdirChange), dirRoot);
                    }
                    catch {
                        ((IDisposable)_dirMonSubdirs).Dispose();
                        _dirMonSubdirs = null;
                        throw;
                    }
 
                    _dirMonSpecialDirs = new ArrayList();
                    for (int i=0; i<s_dirsToMonitor.Length; i++) {
                        _dirMonSpecialDirs.Add(ListenToSubdirectoryChanges(dirRoot, s_dirsToMonitor[i]));
                    }
                }
                finally {
                    _lockDispose.ReleaseReaderLock();
                }
            }
        }
 
        //
        // Monitor a directory that causes an appdomain shutdown when it changes
        //
        internal void StartListeningToLocalResourcesDirectory(VirtualPath virtualDir) {
            Debug.Trace("FileChangesMonitor", "StartListeningToVirtualSubdirectory\n" + "\tArgs: virtualDir=" + virtualDir);
 
            if (IsFCNDisabled) {
                return;
            }
 
            // In some situation (not well understood yet), we get here with either
            // _callbackRenameOrCriticaldirChange or _dirMonSpecialDirs being null (VSWhidbey #215040).
            // When that happens, just return.
            //Debug.Assert(_callbackRenameOrCriticaldirChange != null);
            //Debug.Assert(_dirMonSpecialDirs != null);
            if (_callbackRenameOrCriticaldirChange == null || _dirMonSpecialDirs == null)
                return;
 
            using (new ApplicationImpersonationContext()) {
                _lockDispose.AcquireReaderLock();
                try {
                    if (_disposed) {
                        return;
                    }
 
                    // Get the physical path, and split it into the parent dir and the dir name
                    string dir = virtualDir.MapPath();
                    dir = FileUtil.RemoveTrailingDirectoryBackSlash(dir);
                    string name = Path.GetFileName(dir);
                    dir = Path.GetDirectoryName(dir);
 
                    // If the physical parent directory doesn't exist, don't do anything.
                    // This could happen when using a non-file system based VirtualPathProvider
                    if (!Directory.Exists(dir))
                        return;
 
                    _dirMonSpecialDirs.Add(ListenToSubdirectoryChanges(dir, name));
                }
                finally {
                    _lockDispose.ReleaseReaderLock();
                }
            }
        }
 
        DirectoryMonitor ListenToSubdirectoryChanges(string dirRoot, string dirToListenTo) {
 
            string dirRootSubDir;
            DirectoryMonitor dirMonSubDir;
 
            if (StringUtil.StringEndsWith(dirRoot, '\\')) {
                dirRootSubDir = dirRoot + dirToListenTo;
            }
            else {
                dirRootSubDir = dirRoot + "\\" + dirToListenTo;
            }
 
            if (IsBeneathAppPathInternal(dirRootSubDir)) {
                dirMonSubDir = _dirMonAppPathInternal;
 
                dirToListenTo = dirRootSubDir.Substring(_appPathInternal.Length+1);
                Debug.Trace("ListenToSubDir", dirRoot + " " + dirToListenTo);
                dirMonSubDir.StartMonitoringFileWithAssert(dirToListenTo, new FileChangeEventHandler(this.OnCriticaldirChange), dirRootSubDir);
            }
            else if (Directory.Exists(dirRootSubDir)) {
                dirMonSubDir = new DirectoryMonitor(dirRootSubDir, true, UnsafeNativeMethods.RDCW_FILTER_FILE_CHANGES, _FCNMode);
                try {
                    dirMonSubDir.StartMonitoringFileWithAssert(null, new FileChangeEventHandler(this.OnCriticaldirChange), dirRootSubDir);
                }
                catch {
                    ((IDisposable)dirMonSubDir).Dispose();
                    dirMonSubDir = null;
                    throw;
                }
            }
            else {
                dirMonSubDir = (DirectoryMonitor)_subDirDirMons[dirRoot];
                if (dirMonSubDir == null) {
                    dirMonSubDir = new DirectoryMonitor(dirRoot, false, UnsafeNativeMethods.RDCW_FILTER_FILE_AND_DIR_CHANGES, _FCNMode);
                    _subDirDirMons[dirRoot] = dirMonSubDir;
                }
 
                try {
                    dirMonSubDir.StartMonitoringFileWithAssert(dirToListenTo, new FileChangeEventHandler(this.OnCriticaldirChange), dirRootSubDir);
                }
                catch {
                    ((IDisposable)dirMonSubDir).Dispose();
                    dirMonSubDir = null;
                    throw;
                }
            }
 
            return dirMonSubDir;
        }
 
        void OnSubdirChange(Object sender, FileChangeEvent e) {
            try {
                Interlocked.Increment(ref _activeCallbackCount);
 
                if (_disposed) {
                    return;
                }
 
                Debug.Trace("FileChangesMonitor", "OnSubdirChange\n" + "\tArgs: Action=" + e.Action + "; fileName=" + e.FileName);
                FileChangeEventHandler handler = _callbackRenameOrCriticaldirChange;
                if (    handler != null &&
                        (e.Action == FileAction.Error || e.Action == FileAction.Overwhelming || e.Action == FileAction.RenamedOldName || e.Action == FileAction.Removed)) {
                    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) + ")");
                    
                    HttpRuntime.SetShutdownMessage(
                        SR.GetString(SR.Directory_rename_notification, e.FileName));
                    
                    handler(this, e);
                }
            }
            finally {
                Interlocked.Decrement(ref _activeCallbackCount);
            }
        }
 
        void OnCriticaldirChange(Object sender, FileChangeEvent e) {
            try {
                Interlocked.Increment(ref _activeCallbackCount);
 
                if (_disposed) {
                    return;
                }
 
                Debug.Trace("FileChangesMonitor", "OnCriticaldirChange\n" + "\tArgs: Action=" + e.Action + "; fileName=" + e.FileName);
                HttpRuntime.SetShutdownMessage(SR.GetString(SR.Change_notification_critical_dir));
                FileChangeEventHandler handler = _callbackRenameOrCriticaldirChange;
                if (handler != null) {
                    handler(this, e);
                }
            }
            finally {
                Interlocked.Decrement(ref _activeCallbackCount);
            }
        }
 
        //
        // Request to stop monitoring a file.
        //
        internal void StopMonitoringFile(string alias, object target) {
            Debug.Trace("FileChangesMonitor", "StopMonitoringFile\n" + "File=" + alias + "; Callback=" + target);
 
            if (IsFCNDisabled) {
                return;
            }
 
            FileMonitor         fileMon;
            DirectoryMonitor    dirMon = null;
            string              fullPathName, file = null, dir;
 
            if (alias == null) {
                throw new HttpException(SR.GetString(SR.Invalid_file_name_for_monitoring, String.Empty));
            }
 
            using (new ApplicationImpersonationContext()) {
                _lockDispose.AcquireReaderLock();
                try {
                    if (_disposed) {
                        return;
                    }
 
                    fileMon = (FileMonitor)_aliases[alias];
                    if (fileMon != null && !fileMon.IsDirectory) {
                        // Used the cached directory monitor and file name
                        dirMon = fileMon.DirectoryMonitor;
                        file = fileMon.FileNameLong;
                    }
                    else {
                        if (alias.Length == 0 || !UrlPath.IsAbsolutePhysicalPath(alias)) {
                            throw new HttpException(SR.GetString(SR.Invalid_file_name_for_monitoring, HttpRuntime.GetSafePath(alias)));
                        }
 
                        // Lookup the directory monitor
                        fullPathName = GetFullPath(alias);
                        dir = UrlPath.GetDirectoryOrRootName(fullPathName);
                        file = Path.GetFileName(fullPathName);
                        if (String.IsNullOrEmpty(file)) {
                            // not a file
                            throw new HttpException(SR.GetString(SR.Invalid_file_name_for_monitoring, HttpRuntime.GetSafePath(alias)));
                        }
 
                        dirMon = FindDirectoryMonitor(dir, false, false);
                    }
 
                    if (dirMon != null) {
                        dirMon.StopMonitoringFile(file, target);
                    }
                }
                finally {
                    _lockDispose.ReleaseReaderLock();
                }
            }
        }
 
        //
        // Request to stop monitoring a file.
        // 
        internal void StopMonitoringPath(String alias, object target) {
            Debug.Trace("FileChangesMonitor", "StopMonitoringFile\n" + "File=" + alias + "; Callback=" + target);
 
            if (IsFCNDisabled) {
                return;
            }
 
            FileMonitor         fileMon;
            DirectoryMonitor    dirMon = null;
            string              fullPathName, file = null, dir;
 
            if (alias == null) {
                throw new HttpException(SR.GetString(SR.Invalid_file_name_for_monitoring, String.Empty));
            }
 
            using (new ApplicationImpersonationContext()) {
                _lockDispose.AcquireReaderLock();
                try {
                    if (_disposed) {
                        return;
                    }
 
                    fileMon = (FileMonitor)_aliases[alias];
                    if (fileMon != null) {
                        // Used the cached directory monitor and file name.
                        dirMon = fileMon.DirectoryMonitor;
                        file = fileMon.FileNameLong;
                    }
                    else {
                        if (alias.Length == 0 || !UrlPath.IsAbsolutePhysicalPath(alias)) {
                            throw new HttpException(SR.GetString(SR.Invalid_file_name_for_monitoring, HttpRuntime.GetSafePath(alias)));
                        }
 
                        // try treating the path as a directory
                        fullPathName = GetFullPath(alias);
                        dirMon = FindDirectoryMonitor(fullPathName, false, false);
                        if (dirMon == null) {
                            // try treaing the path as a file
                            dir = UrlPath.GetDirectoryOrRootName(fullPathName);
                            file = Path.GetFileName(fullPathName);
                            if (!String.IsNullOrEmpty(file)) {
                                dirMon = FindDirectoryMonitor(dir, false, false);
                            }
                        }
                    }
 
                    if (dirMon != null) {
                        dirMon.StopMonitoringFile(file, target);
                    }
                }
                finally {
                    _lockDispose.ReleaseReaderLock();
                }
            }
        }
 
         //
         // Returns the last modified time of the file. If the 
         // file does not exist, returns DateTime.MinValue.
         //
         internal FileAttributesData GetFileAttributes(string alias) {
             FileMonitor        fileMon;
             DirectoryMonitor   dirMon = null;
             string             fullPathName, file = null, dir;
             FileAttributesData fad = null;
 
             if (alias == null) {
                 throw FileChangesMonitor.CreateFileMonitoringException(HResults.E_INVALIDARG, alias);
             }
 
             if (IsFCNDisabled) {
                 if (alias.Length == 0 || !UrlPath.IsAbsolutePhysicalPath(alias)) {
                     throw FileChangesMonitor.CreateFileMonitoringException(HResults.E_INVALIDARG, alias);
                 }
 
                 fullPathName = GetFullPath(alias);
                 FindFileData ffd = null;
                 int hr = FindFileData.FindFile(fullPathName, out ffd);
                 if (hr == HResults.S_OK) {
                     return ffd.FileAttributesData;
                 }
                 else {
                     return null;
                 }   
             }
 
             using (new ApplicationImpersonationContext()) {
                 _lockDispose.AcquireReaderLock();
                try {
                    if (!_disposed) {
                        fileMon = (FileMonitor)_aliases[alias];
                        if (fileMon != null && !fileMon.IsDirectory) {
                            // Used the cached directory monitor and file name.
                            dirMon = fileMon.DirectoryMonitor;
                            file = fileMon.FileNameLong;
                        }
                        else {
                            if (alias.Length == 0 || !UrlPath.IsAbsolutePhysicalPath(alias)) {
                                throw FileChangesMonitor.CreateFileMonitoringException(HResults.E_INVALIDARG, alias);
                            }
 
                            // Lookup the directory monitor
                            fullPathName = GetFullPath(alias);
                            dir = UrlPath.GetDirectoryOrRootName(fullPathName);
                            file = Path.GetFileName(fullPathName);
                            if (!String.IsNullOrEmpty(file)) {
                                dirMon = FindDirectoryMonitor(dir, false, false);
                            }
                        }
                    }
                 }
                 finally {
                     _lockDispose.ReleaseReaderLock();
                 }
 
                 // If we're not monitoring the file, get the attributes.
                 if (dirMon == null || !dirMon.GetFileAttributes(file, out fad)) {
                     FileAttributesData.GetFileAttributes(alias, out fad);
                 }
 
                 return fad;
             }
        }
 
        //
        // Request to stop monitoring everything -- release all native resources
        //
        internal void Stop() {
            Debug.Trace("FileChangesMonitor", "Stop!");
 
             if (IsFCNDisabled) {
                 return;
             }
 
            using (new ApplicationImpersonationContext()) {
                _lockDispose.AcquireWriterLock();
                try {
                    _disposed = true;
                }
                finally {
                    _lockDispose.ReleaseWriterLock();
                }
 
                // wait for executing callbacks to complete
                while(_activeCallbackCount != 0) {
                    Thread.Sleep(250);
                }
 
                if (_dirMonSubdirs != null) {
                    _dirMonSubdirs.StopMonitoring();
                    _dirMonSubdirs = null;
                }
 
                if (_dirMonSpecialDirs != null) {
                    foreach (DirectoryMonitor dirMon in _dirMonSpecialDirs) {
                        if (dirMon != null) {
                            dirMon.StopMonitoring();
                        }
                    }
 
                    _dirMonSpecialDirs = null;
                }
 
                _callbackRenameOrCriticaldirChange = null;
 
                if (_dirs != null) {
                    IDictionaryEnumerator e = _dirs.GetEnumerator();
                    while (e.MoveNext()) {
                        DirectoryMonitor dirMon = (DirectoryMonitor)e.Value;
                        dirMon.StopMonitoring();
                    }
                }
 
                _dirs.Clear();
                _aliases.Clear();
 
                // Don't allow the AppDomain to unload while we have
                // active DirMonCompletions
                while (DirMonCompletion.ActiveDirMonCompletions != 0) {
                    Thread.Sleep(10);
                }
            }
 
            Debug.Dump("FileChangesMonitor", this);
        }
 
#if DBG
        internal string DebugDescription(string indent) {
            StringBuilder   sb = new StringBuilder(200);
            string          i2 = indent + "    ";
            DictionaryEntryCaseInsensitiveComparer  decomparer = new DictionaryEntryCaseInsensitiveComparer();
 
            sb.Append(indent + "System.Web.FileChangesMonitor\n");
            if (_dirMonSubdirs != null) {
                sb.Append(indent + "_dirMonSubdirs\n");
                sb.Append(_dirMonSubdirs.DebugDescription(i2));
            }
 
            if (_dirMonSpecialDirs != null) {
                for (int i=0; i<s_dirsToMonitor.Length; i++) {
                    if (_dirMonSpecialDirs[i] != null) {
                        sb.Append(indent + "_dirMon" + s_dirsToMonitor[i] + "\n");
                        sb.Append(((DirectoryMonitor)_dirMonSpecialDirs[i]).DebugDescription(i2));
                    }
                }
            }
 
            sb.Append(indent + "_dirs " + _dirs.Count + " directory monitors...\n");
 
            DictionaryEntry[] dirEntries = new DictionaryEntry[_dirs.Count];
            _dirs.CopyTo(dirEntries, 0);
            Array.Sort(dirEntries, decomparer);
            
            foreach (DictionaryEntry d in dirEntries) {
                DirectoryMonitor dirMon = (DirectoryMonitor)d.Value;
                sb.Append(dirMon.DebugDescription(i2));
            }
 
            return sb.ToString();
        }
#endif
 
#else // !FEATURE_PAL stubbing

        internal static string[] s_dirsToMonitor = new string[] {
        };
 
        internal DateTime StartMonitoringFile(string alias, FileChangeEventHandler callback)
        {
            return DateTime.Now;
        }
        
        internal DateTime StartMonitoringPath(string alias, FileChangeEventHandler callback)
        {
            return DateTime.Now;
        }
 
        internal void StopMonitoringPath(String alias, object target) 
        {
        }
 
        internal void StartMonitoringDirectoryRenamesAndBinDirectory(string dir, FileChangeEventHandler callback) 
        {
        }
        
        internal void Stop() 
        {
        }                
 
#endif // !FEATURE_PAL
    }
 
#if DBG
    internal sealed class DictionaryEntryCaseInsensitiveComparer : IComparer {
        IComparer _cicomparer = StringComparer.OrdinalIgnoreCase;
 
        internal DictionaryEntryCaseInsensitiveComparer() {}
        
        int IComparer.Compare(object x, object y) {
            string a = (string) ((DictionaryEntry) x).Key;
            string b = (string) ((DictionaryEntry) y).Key;
 
            if (a != null && b != null) {
                return _cicomparer.Compare(a, b);
            }
            else {
                return InvariantComparer.Default.Compare(a, b);            
            }
        }
    }
#endif
 
#if DBG
    internal sealed class DictionaryEntryTypeComparer : IComparer {
        IComparer _cicomparer = StringComparer.OrdinalIgnoreCase;
 
        internal DictionaryEntryTypeComparer() {}
 
        int IComparer.Compare(object x, object y) {
            object a = ((DictionaryEntry) x).Key;
            object b = ((DictionaryEntry) y).Key;
 
            string i = null, j = null;
            if (a != null) {
                i = a.GetType().ToString();
            }
 
            if (b != null) {
                j = b.GetType().ToString();
            }
 
            if (i != null && j != null) {
                return _cicomparer.Compare(i, j);
            }
            else {
                return InvariantComparer.Default.Compare(i, j);            
            }
        }
    }
#endif
}