Code:
/ FXUpdate3074 / FXUpdate3074 / 1.1 / DEVDIV / depot / DevDiv / releases / whidbey / QFE / ndp / fx / src / xsp / System / Web / FileChangesMonitor.cs / 8 / 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 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("");
}
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
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
}
#if UNUSED_CODE
// Delegate for receiving raw file change notifications
delegate void RawFileChangeEventHandler(FileAction action, string directory, string filename);
#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 {
#if UNUSED_CODE
internal static event RawFileChangeEventHandler FileChanged; // listener for raw notifications
#endif
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
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) {
Directory = dir;
_fileMons = new Hashtable(StringComparer.OrdinalIgnoreCase);
_watchSubtree = watchSubtree;
_notifyFilter = notifyFilter;
}
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;
}
internal FileMonitor StartMonitoringFile(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
#if UNUSED_CODE
// If anyone is listening to raw file change notifications, fire the event.
// This is used by BuildManagerHost.
if (FileChanged != null)
FileChanged(action, Directory, fileName);
#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 (
bool ignoreThisChangeNotification = false;
if (!isSpecialDirectoryChange && fileName != null && action == FileAction.Modified) {
//
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;
}
}
// 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
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
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
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 race 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
}
//
// 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.StartMonitoringFile(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.StartMonitoringFile(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.StartMonitoringFile(file, callback, alias);
continue;
}
// try treating the path as a directory
dirMon = FindDirectoryMonitor(fullPathName, false, false);
if (dirMon != null) {
fileMon = dirMon.StartMonitoringFile(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.StartMonitoringFile(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.StartMonitoringFile(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);
try {
_dirMonSubdirs.StartMonitoringFile(null, new FileChangeEventHandler(this.OnSubdirChange), dirRoot);
}
catch {
((IDisposable)_dirMonSubdirs).Dispose();
_dirMonSubdirs = null;
throw;
}
_dirMonSpecialDirs = new ArrayList();
for (int i=0; i
// 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 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("");
}
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
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
}
#if UNUSED_CODE
// Delegate for receiving raw file change notifications
delegate void RawFileChangeEventHandler(FileAction action, string directory, string filename);
#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 {
#if UNUSED_CODE
internal static event RawFileChangeEventHandler FileChanged; // listener for raw notifications
#endif
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
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) {
Directory = dir;
_fileMons = new Hashtable(StringComparer.OrdinalIgnoreCase);
_watchSubtree = watchSubtree;
_notifyFilter = notifyFilter;
}
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;
}
internal FileMonitor StartMonitoringFile(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
#if UNUSED_CODE
// If anyone is listening to raw file change notifications, fire the event.
// This is used by BuildManagerHost.
if (FileChanged != null)
FileChanged(action, Directory, fileName);
#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 (
bool ignoreThisChangeNotification = false;
if (!isSpecialDirectoryChange && fileName != null && action == FileAction.Modified) {
//
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;
}
}
// 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
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
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
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 race 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
}
//
// 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.StartMonitoringFile(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.StartMonitoringFile(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.StartMonitoringFile(file, callback, alias);
continue;
}
// try treating the path as a directory
dirMon = FindDirectoryMonitor(fullPathName, false, false);
if (dirMon != null) {
fileMon = dirMon.StartMonitoringFile(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.StartMonitoringFile(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.StartMonitoringFile(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);
try {
_dirMonSubdirs.StartMonitoringFile(null, new FileChangeEventHandler(this.OnSubdirChange), dirRoot);
}
catch {
((IDisposable)_dirMonSubdirs).Dispose();
_dirMonSubdirs = null;
throw;
}
_dirMonSpecialDirs = new ArrayList();
for (int i=0; i
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- CompilerErrorCollection.cs
- DataSourceControlBuilder.cs
- SymbolUsageManager.cs
- PageRanges.cs
- DCSafeHandle.cs
- CreateParams.cs
- CodeIndexerExpression.cs
- SystemColors.cs
- ClientTargetSection.cs
- WebBrowserNavigatingEventHandler.cs
- WpfGeneratedKnownProperties.cs
- HttpModuleCollection.cs
- CodeTypeReferenceExpression.cs
- StaticTextPointer.cs
- FormatterConverter.cs
- ModelEditingScope.cs
- IgnoreDeviceFilterElementCollection.cs
- HtmlEmptyTagControlBuilder.cs
- ZoneIdentityPermission.cs
- CrossAppDomainChannel.cs
- AssemblyCollection.cs
- ToolStripItem.cs
- ThaiBuddhistCalendar.cs
- ResolveMatchesMessage11.cs
- PeerNameResolver.cs
- MimeTypePropertyAttribute.cs
- WinEventQueueItem.cs
- ColumnMap.cs
- Int32Converter.cs
- SourceChangedEventArgs.cs
- IncrementalCompileAnalyzer.cs
- MethodRental.cs
- GenericAuthenticationEventArgs.cs
- CheckBoxBaseAdapter.cs
- PropertiesTab.cs
- SharedUtils.cs
- SimpleHandlerFactory.cs
- AsyncStreamReader.cs
- _NestedSingleAsyncResult.cs
- GeneratedContractType.cs
- ByteKeyFrameCollection.cs
- Propagator.Evaluator.cs
- LayoutEditorPart.cs
- TreeNodeMouseHoverEvent.cs
- SamlSerializer.cs
- PositiveTimeSpanValidator.cs
- BinaryUtilClasses.cs
- TextContainerChangeEventArgs.cs
- FileDialog.cs
- AppDomainUnloadedException.cs
- DataTableReaderListener.cs
- Double.cs
- ParserContext.cs
- SchemaTableOptionalColumn.cs
- GenericWebPart.cs
- httpapplicationstate.cs
- LinqDataSourceContextData.cs
- UInt32.cs
- ColorTransformHelper.cs
- GroupBoxRenderer.cs
- FixedPageStructure.cs
- WmpBitmapDecoder.cs
- LoginView.cs
- BindingSourceDesigner.cs
- MeshGeometry3D.cs
- UIElement3D.cs
- WebPartDisplayMode.cs
- LinqDataSourceStatusEventArgs.cs
- CodeDomConfigurationHandler.cs
- EdmError.cs
- CodeCompiler.cs
- StylusPointPropertyId.cs
- documentsequencetextview.cs
- ItemTypeToolStripMenuItem.cs
- Ticks.cs
- WmlObjectListAdapter.cs
- DataStreamFromComStream.cs
- DbBuffer.cs
- PageStatePersister.cs
- DataStreams.cs
- DataGridViewButtonCell.cs
- UiaCoreApi.cs
- InlineUIContainer.cs
- oledbconnectionstring.cs
- webeventbuffer.cs
- OverrideMode.cs
- SelectionProviderWrapper.cs
- ViewManagerAttribute.cs
- ModifierKeysConverter.cs
- ThousandthOfEmRealDoubles.cs
- ObjectAnimationBase.cs
- SqlFacetAttribute.cs
- XmlAttributeCache.cs
- altserialization.cs
- BindingMemberInfo.cs
- DetailsViewUpdateEventArgs.cs
- TextEffectCollection.cs
- Popup.cs
- GenericTypeParameterBuilder.cs
- TransformerConfigurationWizardBase.cs