/mcs/class/referencesource/System.Web/FileChangesMonitor.cs
C# | 1138 lines | 774 code | 169 blank | 195 comment | 198 complexity | be2c73d2645a5714ac0794dca9445b38 MD5 | raw file
Possible License(s): LGPL-2.0, MPL-2.0-no-copyleft-exception, CC-BY-SA-3.0, GPL-2.0
- //------------------------------------------------------------------------------
- // <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",
-