FileChangesMonitor.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / fx / src / xsp / System / Web / FileChangesMonitor.cs / 1305376 / FileChangesMonitor.cs

                            //------------------------------------------------------------------------------ 
// 
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// 
//----------------------------------------------------------------------------- 

namespace System.Web { 
    using System.Text; 
    using System.Globalization;
    using System.Threading; 
    using System.Runtime.InteropServices;
    using System.Collections;
    using System.Collections.Specialized;
    using System.Web.Util; 
    using System.IO;
    using System.Security; 
    using System.Security.Permissions; 
    using System.Web.Hosting;
    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];
         }

        static internal byte[] GetDacl(string filename) { 
            // Get the size needed
            int lengthNeeded = 0; 
            int fOK = UnsafeNativeMethods.GetFileSecurity(filename, DACL_INFORMATION, null, 0, ref lengthNeeded); 
            int err = Marshal.GetLastWin32Error();
 
            // If no size is needed, return a non-null marker
            if (fOK != 0) {
                Debug.Trace("GetDacl", "Returning null dacl");
                return s_nullDacl; 
            }
 
            // Ensure the error is E_INSUFFICIENT_BUFFER 
            int hr = HttpException.HResultFromLastError(err);
            if (hr != HResults.E_INSUFFICIENT_BUFFER) { 
                Debug.Trace("GetDacl", "Error in first call to GetFileSecurity: 0x" + hr.ToString("x", NumberFormatInfo.InvariantInfo));
                return null;
            }
 
            // Get the buffer this time
            byte[] 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 [....] 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 [....] 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("");
            }
            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 = \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 

        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); 

            // 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.AppDomainAppIdInternal, watchSubtree, notifyFilter, 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"); 
            HandleRef ndirMonCompletionHandle = _ndirMonCompletionHandle; 
            if (ndirMonCompletionHandle.Handle != IntPtr.Zero) {
                _ndirMonCompletionHandle = new HandleRef(this, IntPtr.Zero); 
                UnsafeNativeMethods.DirMonClose(ndirMonCompletionHandle);
                Interlocked.Decrement(ref _activeDirMonCompletions);
            }
        } 

        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 we're already disposing (meaning Dispose is called, and 
            // _ndirMonCompletionHandle.Handle was set to IntPtr.Zero), and OnFileChange is
            // called due to a real file notification, we won't process it in order to 
            // speed up the execution of the completion thread. We need this because native
            // completion threads may stil call into managed code after we start
            // shutting down the whole appdomain. By speeding up their execution,
            // we hope we will avoid a situation where they call into managed code 
            // after we've unloaded the appdomain.
            if (action == FileAction.Dispose) { 
                if (_rootCallback.IsAllocated) { 
                    _rootCallback.Free();
                } 
            }
            else if (_ndirMonCompletionHandle.Handle != IntPtr.Zero) {
                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)
 
        // constructor for special dirmon that monitors all files and subdirectories beneath the vroot (enabled via FCNMode registry key)
        internal DirectoryMonitor(string appPathInternal): this(appPathInternal, true, UnsafeNativeMethods.RDCW_FILTER_FILE_AND_DIR_CHANGES) { 
            _isDirMonAppPathInternal = true; 
        }
 
        internal DirectoryMonitor(string dir, bool watchSubtree, uint notifyFilter): this(dir, watchSubtree, notifyFilter, false) {
        }

        internal DirectoryMonitor(string dir, bool watchSubtree, uint notifyFilter, bool ignoreSubdirChange) { 
            Directory = dir;
            _fileMons = new Hashtable(StringComparer.OrdinalIgnoreCase); 
            _watchSubtree = watchSubtree; 
            _notifyFilter = notifyFilter;
            _ignoreSubdirChange = ignoreSubdirChange; 
        }

        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  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) {
                                HttpRuntime.SetShutdownMessage("Overwhelming Change Notification in " + Directory);
                                //increase file notification buffer size, but only once per app instance
                                if (Interlocked.Increment(ref s_notificationBufferSizeIncreased) == 1) { 
                                    UnsafeNativeMethods.GrowFileNotificationBuffer( HttpRuntime.AppDomainAppIdInternal, _watchSubtree );
                                } 
                            } 
                            else if (action == FileAction.Error) {
                                HttpRuntime.SetShutdownMessage("File Change Notification Error in " + Directory); 
                            }

                            // 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.Exists) {
                                    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 || action == FileAction.Overwhelming) {
                        // 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") : "") +
                                    "\nfileMon=" + ((fileMon != null) ? fileMon.DebugDescription("\t") : "") +
                                    "\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") : "") + 
                                    "\nfileMon=" + ((fileMon != null) ? fileMon.DebugDescription("\t") : "") +
                                    "\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 = \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 

        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() { 
            // 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
            UnsafeNativeMethods.GetDirMonConfiguration(out _FCNMode);
            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);
            } 
 
#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); 
                        _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); 
                    try {
                        _dirMonSubdirs.StartMonitoringFileWithAssert(null, new FileChangeEventHandler(this.OnSubdirChange), dirRoot); 
                    } 
                    catch {
                        ((IDisposable)_dirMonSubdirs).Dispose(); 
                        _dirMonSubdirs = null;
                        throw;
                    }
 
                    _dirMonSpecialDirs = new ArrayList();
                    for (int i=0; i

                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK