PageRenderTime 60ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/mcs/class/referencesource/System.Web/FileChangesMonitor.cs

https://github.com/pruiz/mono
C# | 1138 lines | 774 code | 169 blank | 195 comment | 198 complexity | be2c73d2645a5714ac0794dca9445b38 MD5 | raw file
Possible License(s): LGPL-2.0, MPL-2.0-no-copyleft-exception, CC-BY-SA-3.0, GPL-2.0
  1. //------------------------------------------------------------------------------
  2. // <copyright file="FileChangesMonitor.cs" company="Microsoft">
  3. // Copyright (c) Microsoft Corporation. All rights reserved.
  4. // </copyright>
  5. //------------------------------------------------------------------------------
  6. namespace System.Web {
  7. using System.Collections;
  8. using System.Collections.Specialized;
  9. using System.Diagnostics.CodeAnalysis;
  10. using System.Globalization;
  11. using System.IO;
  12. using System.Runtime.InteropServices;
  13. using System.Security;
  14. using System.Security.Permissions;
  15. using System.Text;
  16. using System.Threading;
  17. using System.Web.Configuration;
  18. using System.Web.Hosting;
  19. using System.Web.Util;
  20. using Microsoft.Win32;
  21. // Type of the callback to the subscriber of a file change event in FileChangesMonitor.StartMonitoringFile
  22. delegate void FileChangeEventHandler(Object sender, FileChangeEvent e);
  23. // The type of file change that occurred.
  24. enum FileAction {
  25. Dispose = -2,
  26. Error = -1,
  27. Overwhelming = 0,
  28. Added = 1,
  29. Removed = 2,
  30. Modified = 3,
  31. RenamedOldName = 4,
  32. RenamedNewName = 5
  33. }
  34. // Event data for a file change notification
  35. sealed class FileChangeEvent : EventArgs {
  36. internal FileAction Action; // the action
  37. internal string FileName; // the file that caused the action
  38. internal FileChangeEvent(FileAction action, string fileName) {
  39. this.Action = action;
  40. this.FileName = fileName;
  41. }
  42. }
  43. // Contains information about the target of a file change notification
  44. sealed class FileMonitorTarget {
  45. internal readonly FileChangeEventHandler Callback; // the callback
  46. internal readonly string Alias; // the filename used to name the file
  47. internal readonly DateTime UtcStartMonitoring;// time we started monitoring
  48. int _refs; // number of uses of callbacks
  49. internal FileMonitorTarget(FileChangeEventHandler callback, string alias) {
  50. Callback = callback;
  51. Alias = alias;
  52. UtcStartMonitoring = DateTime.UtcNow;
  53. _refs = 1;
  54. }
  55. internal int AddRef() {
  56. _refs++;
  57. return _refs;
  58. }
  59. internal int Release() {
  60. _refs--;
  61. return _refs;
  62. }
  63. #if DBG
  64. internal string DebugDescription(string indent) {
  65. StringBuilder sb = new StringBuilder(200);
  66. string i2 = indent + " ";
  67. sb.Append(indent + "FileMonitorTarget\n");
  68. sb.Append(i2 + " Callback: " + Callback.Target + "(HC=" + Callback.Target.GetHashCode().ToString("x", NumberFormatInfo.InvariantInfo) + ")\n");
  69. sb.Append(i2 + " Alias: " + Alias + "\n");
  70. sb.Append(i2 + "StartMonitoring: " + Debug.FormatUtcDate(UtcStartMonitoring) + "\n");
  71. sb.Append(i2 + " _refs: " + _refs + "\n");
  72. return sb.ToString();
  73. }
  74. #endif
  75. }
  76. #if !FEATURE_PAL // FEATURE_PAL does not enable access control
  77. sealed class FileSecurity {
  78. const int DACL_INFORMATION =
  79. UnsafeNativeMethods.DACL_SECURITY_INFORMATION |
  80. UnsafeNativeMethods.GROUP_SECURITY_INFORMATION |
  81. UnsafeNativeMethods.OWNER_SECURITY_INFORMATION;
  82. static Hashtable s_interned;
  83. static byte[] s_nullDacl;
  84. class DaclComparer : IEqualityComparer {
  85. // Compares two objects. An implementation of this method must return a
  86. // value less than zero if x is less than y, zero if x is equal to y, or a
  87. // value greater than zero if x is greater than y.
  88. //
  89. private int Compare(byte[] a, byte[] b) {
  90. int result = a.Length - b.Length;
  91. for (int i = 0; result == 0 && i < a.Length ; i++) {
  92. result = a[i] - b[i];
  93. }
  94. return result;
  95. }
  96. bool IEqualityComparer.Equals(Object x, Object y) {
  97. if (x == null && y == null) {
  98. return true;
  99. }
  100. if (x == null || y == null) {
  101. return false;
  102. }
  103. byte[] a = x as byte[];
  104. byte[] b = y as byte[];
  105. if (a == null || b == null) {
  106. return false;
  107. }
  108. return Compare(a, b) == 0;
  109. }
  110. int IEqualityComparer.GetHashCode(Object obj) {
  111. byte[] a = (byte[]) obj;
  112. HashCodeCombiner combiner = new HashCodeCombiner();
  113. foreach (byte b in a) {
  114. combiner.AddObject(b);
  115. }
  116. return combiner.CombinedHash32;
  117. }
  118. }
  119. static FileSecurity() {
  120. s_interned = new Hashtable(0, 1.0f, new DaclComparer());
  121. s_nullDacl = new byte[0];
  122. }
  123. [SuppressMessage("Microsoft.Interoperability", "CA1404:CallGetLastErrorImmediatelyAfterPInvoke",
  124. Justification="Microsoft: Call to GetLastWin32Error() does follow P/Invoke call that is outside the if/else block.")]
  125. static internal byte[] GetDacl(string filename) {
  126. // DevDiv #322858 - allow skipping DACL step for perf gain
  127. if (HostingEnvironment.FcnSkipReadAndCacheDacls) {
  128. return s_nullDacl;
  129. }
  130. // DevDiv #246973
  131. // Perf: Start with initial buffer size to minimize File IO
  132. //
  133. int lengthNeeded = 512;
  134. byte[] dacl = new byte[lengthNeeded];
  135. int fOK = UnsafeNativeMethods.GetFileSecurity(filename, DACL_INFORMATION, dacl, dacl.Length, ref lengthNeeded);
  136. if (fOK != 0) {
  137. // If no size is needed, return a non-null marker
  138. if (lengthNeeded == 0) {
  139. Debug.Trace("GetDacl", "Returning null dacl");
  140. return s_nullDacl;
  141. }
  142. // Shrink the buffer to fit the whole data
  143. Array.Resize(ref dacl, lengthNeeded);
  144. }
  145. else {
  146. int hr = HttpException.HResultFromLastError(Marshal.GetLastWin32Error());
  147. // Check if need to redo the call with larger buffer
  148. if (hr != HResults.E_INSUFFICIENT_BUFFER) {
  149. Debug.Trace("GetDacl", "Error in first call to GetFileSecurity: 0x" + hr.ToString("x", NumberFormatInfo.InvariantInfo));
  150. return null;
  151. }
  152. // The buffer wasn't large enough. Try again
  153. dacl = new byte[lengthNeeded];
  154. fOK = UnsafeNativeMethods.GetFileSecurity(filename, DACL_INFORMATION, dacl, dacl.Length, ref lengthNeeded);
  155. if (fOK == 0) {
  156. #if DBG
  157. hr = HttpException.HResultFromLastError(Marshal.GetLastWin32Error());
  158. Debug.Trace("GetDacl", "Error in second to GetFileSecurity: 0x" + hr.ToString("x", NumberFormatInfo.InvariantInfo));
  159. #endif
  160. return null;
  161. }
  162. }
  163. byte[] interned = (byte[]) s_interned[dacl];
  164. if (interned == null) {
  165. lock (s_interned.SyncRoot) {
  166. interned = (byte[]) s_interned[dacl];
  167. if (interned == null) {
  168. Debug.Trace("GetDacl", "Interning new dacl, length " + dacl.Length);
  169. interned = dacl;
  170. s_interned[interned] = interned;
  171. }
  172. }
  173. }
  174. Debug.Trace("GetDacl", "Returning dacl, length " + dacl.Length);
  175. return interned;
  176. }
  177. }
  178. // holds information about a single file and the targets of change notification
  179. sealed class FileMonitor {
  180. internal readonly DirectoryMonitor DirectoryMonitor; // the parent
  181. internal readonly HybridDictionary Aliases; // aliases for this file
  182. string _fileNameLong; // long file name - if null, represents any file in this directory
  183. string _fileNameShort; // short file name, may be null
  184. HybridDictionary _targets; // targets of notification
  185. bool _exists; // does the file exist?
  186. FileAttributesData _fad; // file attributes
  187. byte[] _dacl; // dacl
  188. FileAction _lastAction; // last action that ocurred on this file
  189. DateTime _utcLastCompletion; // date of the last RDCW completion
  190. internal FileMonitor(
  191. DirectoryMonitor dirMon, string fileNameLong, string fileNameShort,
  192. bool exists, FileAttributesData fad, byte[] dacl) {
  193. DirectoryMonitor = dirMon;
  194. _fileNameLong = fileNameLong;
  195. _fileNameShort = fileNameShort;
  196. _exists = exists;
  197. _fad = fad;
  198. _dacl = dacl;
  199. _targets = new HybridDictionary();
  200. Aliases = new HybridDictionary(true);
  201. }
  202. internal string FileNameLong {get {return _fileNameLong;}}
  203. internal string FileNameShort {get {return _fileNameShort;}}
  204. internal bool Exists {get {return _exists;}}
  205. internal bool IsDirectory {get {return (FileNameLong == null);}}
  206. internal FileAction LastAction {
  207. get {return _lastAction;}
  208. set {_lastAction = value;}
  209. }
  210. internal DateTime UtcLastCompletion {
  211. get {return _utcLastCompletion;}
  212. set {_utcLastCompletion = value;}
  213. }
  214. // Returns the attributes of a file, updating them if the file has changed.
  215. internal FileAttributesData Attributes {
  216. get {return _fad;}
  217. }
  218. internal byte[] Dacl {
  219. get {return _dacl;}
  220. }
  221. internal void ResetCachedAttributes() {
  222. _fad = null;
  223. _dacl = null;
  224. }
  225. internal void UpdateCachedAttributes() {
  226. string path = Path.Combine(DirectoryMonitor.Directory, FileNameLong);
  227. FileAttributesData.GetFileAttributes(path, out _fad);
  228. _dacl = FileSecurity.GetDacl(path);
  229. }
  230. // Set new file information when a file comes into existence
  231. internal void MakeExist(FindFileData ffd, byte[] dacl) {
  232. _fileNameLong = ffd.FileNameLong;
  233. _fileNameShort = ffd.FileNameShort;
  234. _fad = ffd.FileAttributesData;
  235. _dacl = dacl;
  236. _exists = true;
  237. }
  238. // Remove a file from existence
  239. internal void MakeExtinct() {
  240. _fad = null;
  241. _dacl = null;
  242. _exists = false;
  243. }
  244. internal void RemoveFileNameShort() {
  245. _fileNameShort = null;
  246. }
  247. internal ICollection Targets {
  248. get {return _targets.Values;}
  249. }
  250. // Add delegate for this file.
  251. internal void AddTarget(FileChangeEventHandler callback, string alias, bool newAlias) {
  252. FileMonitorTarget target = (FileMonitorTarget)_targets[callback.Target];
  253. if (target != null) {
  254. target.AddRef();
  255. }
  256. else {
  257. #if DBG
  258. // Needs the lock to sync with DebugDescription
  259. lock (_targets) {
  260. #endif
  261. _targets.Add(callback.Target, new FileMonitorTarget(callback, alias));
  262. #if DBG
  263. }
  264. #endif
  265. }
  266. if (newAlias) {
  267. Aliases[alias] = alias;
  268. }
  269. }
  270. // Remove delegate for this file given the target object.
  271. internal int RemoveTarget(object callbackTarget) {
  272. FileMonitorTarget target = (FileMonitorTarget)_targets[callbackTarget];
  273. #if DBG
  274. if (FileChangesMonitor.s_enableRemoveTargetAssert) {
  275. Debug.Assert(target != null, "removing file monitor target that was never added or already been removed");
  276. }
  277. #endif
  278. if (target != null && target.Release() == 0) {
  279. #if DBG
  280. // Needs the lock to sync with DebugDescription
  281. lock (_targets) {
  282. #endif
  283. _targets.Remove(callbackTarget);
  284. #if DBG
  285. }
  286. #endif
  287. }
  288. return _targets.Count;
  289. }
  290. #if DBG
  291. internal string DebugDescription(string indent) {
  292. StringBuilder sb = new StringBuilder(200);
  293. string i2 = indent + " ";
  294. string i3 = i2 + " ";
  295. DictionaryEntryTypeComparer detcomparer = new DictionaryEntryTypeComparer();
  296. sb.Append(indent + "System.Web.FileMonitor: ");
  297. if (FileNameLong != null) {
  298. sb.Append(FileNameLong);
  299. if (FileNameShort != null) {
  300. sb.Append("; ShortFileName=" + FileNameShort);
  301. }
  302. sb.Append("; FileExists="); sb.Append(_exists);
  303. }
  304. else {
  305. sb.Append("<ANY>");
  306. }
  307. sb.Append("\n");
  308. sb.Append(i2 + "LastAction="); sb.Append(_lastAction);
  309. sb.Append("; LastCompletion="); sb.Append(Debug.FormatUtcDate(_utcLastCompletion));
  310. sb.Append("\n");
  311. if (_fad != null) {
  312. sb.Append(_fad.DebugDescription(i2));
  313. }
  314. else {
  315. sb.Append(i2 + "FileAttributesData = <null>\n");
  316. }
  317. DictionaryEntry[] delegateEntries;
  318. lock (_targets) {
  319. sb.Append(i2 + _targets.Count + " delegates...\n");
  320. delegateEntries = new DictionaryEntry[_targets.Count];
  321. _targets.CopyTo(delegateEntries, 0);
  322. }
  323. Array.Sort(delegateEntries, detcomparer);
  324. foreach (DictionaryEntry d in delegateEntries) {
  325. sb.Append(i3 + "Delegate " + d.Key.GetType() + "(HC=" + d.Key.GetHashCode().ToString("x", NumberFormatInfo.InvariantInfo) + ")\n");
  326. }
  327. return sb.ToString();
  328. }
  329. #endif
  330. }
  331. // Change notifications delegate from native code.
  332. delegate void NativeFileChangeNotification(FileAction action, [In, MarshalAs(UnmanagedType.LPWStr)] string fileName, long ticks);
  333. //
  334. // Wraps N/Direct calls to native code that does completion port
  335. // based ReadDirectoryChangesW().
  336. // This needs to be a separate object so that a DirectoryMonitory
  337. // can start monitoring while the old _rootCallback has not been
  338. // disposed.
  339. //
  340. sealed class DirMonCompletion : IDisposable {
  341. static int _activeDirMonCompletions = 0; // private counter used via reflection by FCN check-in suite
  342. DirectoryMonitor _dirMon; // directory monitor
  343. IntPtr _ndirMonCompletionPtr; // pointer to native dir mon as int (used only to construct HandleRef)
  344. HandleRef _ndirMonCompletionHandle; // handleref of a pointer to native dir mon as int
  345. GCHandle _rootCallback; // roots this callback to prevent collection
  346. int _disposed; // set to 1 when we call DirMonClose
  347. object _ndirMonCompletionHandleLock;
  348. internal static int ActiveDirMonCompletions { get { return _activeDirMonCompletions; } }
  349. internal DirMonCompletion(DirectoryMonitor dirMon, string dir, bool watchSubtree, uint notifyFilter) {
  350. Debug.Trace("FileChangesMonitor", "DirMonCompletion::ctor " + dir + " " + watchSubtree.ToString() + " " + notifyFilter.ToString(NumberFormatInfo.InvariantInfo));
  351. int hr;
  352. NativeFileChangeNotification myCallback;
  353. _dirMon = dirMon;
  354. myCallback = new NativeFileChangeNotification(this.OnFileChange);
  355. _ndirMonCompletionHandleLock = new object();
  356. try {
  357. }
  358. finally {
  359. // protected from ThreadAbortEx
  360. lock(_ndirMonCompletionHandleLock) {
  361. // Dev10 927846: The managed DirMonCompletion.ctor calls DirMonOpen to create and initialize the native DirMonCompletion.
  362. // If this succeeds, the managed DirMonCompletion.ctor creates a GCHandle to root itself so the target of the callback
  363. // stays alive. When the native DirMonCompletion is freed it invokes the managed callback with ACTION_DISPOSE to
  364. // release the GCHandle. In order for the native DirMonCOmpletion to be freed, either DirMonOpen must fail or
  365. // the managed DirMonCompletion.Dispose must be called and it must invoke DirMonClose. Waiting until the native
  366. // DirMonCompletion.dtor is called to release the GCHandle ensures that the directory handle has been closed,
  367. // the i/o completions have finished and there are no other threads calling the managed callback. This is because
  368. // we AddRef when we initiate i/o and we Release when the i/o completion finishes.
  369. // If I don't do this, myCallback will be collected by GC since its only reference is
  370. // from the native code.
  371. _rootCallback = GCHandle.Alloc(myCallback);
  372. hr = UnsafeNativeMethods.DirMonOpen(dir, HttpRuntime.AppDomainAppId, watchSubtree, notifyFilter, dirMon.FcnMode, myCallback, out _ndirMonCompletionPtr);
  373. if (hr != HResults.S_OK) {
  374. _rootCallback.Free();
  375. throw FileChangesMonitor.CreateFileMonitoringException(hr, dir);
  376. }
  377. _ndirMonCompletionHandle = new HandleRef(this, _ndirMonCompletionPtr);
  378. Interlocked.Increment(ref _activeDirMonCompletions);
  379. }
  380. }
  381. }
  382. ~DirMonCompletion() {
  383. Dispose(false);
  384. }
  385. void IDisposable.Dispose() {
  386. Dispose(true);
  387. System.GC.SuppressFinalize(this);
  388. }
  389. void Dispose(bool disposing) {
  390. Debug.Trace("FileChangesMonitor", "DirMonCompletion::Dispose");
  391. // this is here because callbacks from native code can cause
  392. // this to be invoked from multiple threads concurrently, and
  393. // I don't want contention on _ndirMonCompletionHandleLock,
  394. // but also need to ensure that DirMonOpen returns and the
  395. // _ndirMonCompletionHandle is set before DirMonClose is called.
  396. if (Interlocked.Exchange(ref _disposed, 1) == 0) {
  397. // Dev10 927846: There is a small window during which the .ctor has
  398. // not returned from DirMonOpen yet but because we already started
  399. // monitoring, we might receive a change notification which could
  400. // potentially Dispose the instance, so we need to block until
  401. // DirMonOpen returns and _ndirMonCompletionHandler is set
  402. lock(_ndirMonCompletionHandleLock) {
  403. // Dev11 - 364642: if Dispose is called while finalizing for AD unload then
  404. // the native DirMonCompletion won't be able to call back into the appdomain.
  405. // But it does not need to because _rootCallback is already reclaimed as part of AD unload
  406. bool fNeedToSendFileActionDispose = !AppDomain.CurrentDomain.IsFinalizingForUnload();
  407. HandleRef ndirMonCompletionHandle = _ndirMonCompletionHandle;
  408. if (ndirMonCompletionHandle.Handle != IntPtr.Zero) {
  409. _ndirMonCompletionHandle = new HandleRef(this, IntPtr.Zero);
  410. UnsafeNativeMethods.DirMonClose(ndirMonCompletionHandle, fNeedToSendFileActionDispose);
  411. }
  412. }
  413. }
  414. }
  415. void OnFileChange(FileAction action, string fileName, long ticks) {
  416. DateTime utcCompletion;
  417. if (ticks == 0) {
  418. utcCompletion = DateTime.MinValue;
  419. }
  420. else {
  421. utcCompletion = DateTimeUtil.FromFileTimeToUtc(ticks);
  422. }
  423. #if DBG
  424. Debug.Trace("FileChangesMonitorOnFileChange", "Action=" + action + "; Dir=" + _dirMon.Directory + "; fileName=" + Debug.ToStringMaybeNull(fileName) + "; completion=" + Debug.FormatUtcDate(utcCompletion) + ";_ndirMonCompletionPtr=0x" + _ndirMonCompletionPtr.ToString("x"));
  425. #endif
  426. //
  427. // The native DirMonCompletion sends FileAction.Dispose
  428. // when there are no more outstanding calls on the
  429. // delegate. Only then can _rootCallback be freed.
  430. //
  431. if (action == FileAction.Dispose) {
  432. if (_rootCallback.IsAllocated) {
  433. _rootCallback.Free();
  434. }
  435. Interlocked.Decrement(ref _activeDirMonCompletions);
  436. }
  437. else {
  438. using (new ApplicationImpersonationContext()) {
  439. _dirMon.OnFileChange(action, fileName, utcCompletion);
  440. }
  441. }
  442. }
  443. #if DBG
  444. internal string DebugDescription(string indent) {
  445. int hc = ((Delegate)_rootCallback.Target).Target.GetHashCode();
  446. string description = indent + "_ndirMonCompletionPtr=0x" + _ndirMonCompletionPtr.ToString("x") + "; callback=0x" + hc.ToString("x", NumberFormatInfo.InvariantInfo) + "\n";
  447. return description;
  448. }
  449. #endif
  450. }
  451. sealed class NotificationQueueItem {
  452. internal readonly FileChangeEventHandler Callback;
  453. internal readonly string Filename;
  454. internal readonly FileAction Action;
  455. internal NotificationQueueItem(FileChangeEventHandler callback, FileAction action, string filename) {
  456. Callback = callback;
  457. Action = action;
  458. Filename = filename;
  459. }
  460. }
  461. //
  462. // Monitor changes in a single directory.
  463. //
  464. sealed class DirectoryMonitor : IDisposable {
  465. static Queue s_notificationQueue = new Queue();
  466. static WorkItemCallback s_notificationCallback = new WorkItemCallback(FireNotifications);
  467. static int s_inNotificationThread;
  468. static int s_notificationBufferSizeIncreased = 0;
  469. internal readonly string Directory; // directory being monitored
  470. Hashtable _fileMons; // fileName -> FileMonitor
  471. int _cShortNames; // number of file monitors that are added with their short name
  472. FileMonitor _anyFileMon; // special file monitor to watch for any changes in directory
  473. bool _watchSubtree; // watch subtree?
  474. uint _notifyFilter; // the notify filter for the call to ReadDirectoryChangesW
  475. bool _ignoreSubdirChange; // when a subdirectory is deleted or renamed, ignore the notification if we're not monitoring it
  476. DirMonCompletion _dirMonCompletion; // dirmon completion
  477. bool _isDirMonAppPathInternal; // special dirmon that monitors all files and subdirectories beneath the vroot (enabled via FCNMode registry key)
  478. // FcnMode to pass to native code
  479. internal int FcnMode {
  480. get;
  481. set;
  482. }
  483. // constructor for special dirmon that monitors all files and subdirectories beneath the vroot (enabled via FCNMode registry key)
  484. internal DirectoryMonitor(string appPathInternal, int fcnMode): this(appPathInternal, true, UnsafeNativeMethods.RDCW_FILTER_FILE_AND_DIR_CHANGES, fcnMode) {
  485. _isDirMonAppPathInternal = true;
  486. }
  487. internal DirectoryMonitor(string dir, bool watchSubtree, uint notifyFilter, int fcnMode): this(dir, watchSubtree, notifyFilter, false, fcnMode) {
  488. }
  489. internal DirectoryMonitor(string dir, bool watchSubtree, uint notifyFilter, bool ignoreSubdirChange, int fcnMode) {
  490. Directory = dir;
  491. _fileMons = new Hashtable(StringComparer.OrdinalIgnoreCase);
  492. _watchSubtree = watchSubtree;
  493. _notifyFilter = notifyFilter;
  494. _ignoreSubdirChange = ignoreSubdirChange;
  495. FcnMode = fcnMode;
  496. }
  497. void IDisposable.Dispose() {
  498. if (_dirMonCompletion != null) {
  499. ((IDisposable)_dirMonCompletion).Dispose();
  500. _dirMonCompletion = null;
  501. }
  502. //
  503. // Remove aliases to this object in FileChangesMonitor so that
  504. // it is not rooted.
  505. //
  506. if (_anyFileMon != null) {
  507. HttpRuntime.FileChangesMonitor.RemoveAliases(_anyFileMon);
  508. _anyFileMon = null;
  509. }
  510. foreach (DictionaryEntry e in _fileMons) {
  511. string key = (string) e.Key;
  512. FileMonitor fileMon = (FileMonitor) e.Value;
  513. if (fileMon.FileNameLong == key) {
  514. HttpRuntime.FileChangesMonitor.RemoveAliases(fileMon);
  515. }
  516. }
  517. _fileMons.Clear();
  518. _cShortNames = 0;
  519. }
  520. internal bool IsMonitoring() {
  521. return GetFileMonitorsCount() > 0;
  522. }
  523. void StartMonitoring() {
  524. if (_dirMonCompletion == null) {
  525. _dirMonCompletion = new DirMonCompletion(this, Directory, _watchSubtree, _notifyFilter);
  526. }
  527. }
  528. internal void StopMonitoring() {
  529. lock (this) {
  530. ((IDisposable)this).Dispose();
  531. }
  532. }
  533. FileMonitor FindFileMonitor(string file) {
  534. FileMonitor fileMon;
  535. if (file == null) {
  536. fileMon = _anyFileMon;
  537. }
  538. else {
  539. fileMon = (FileMonitor)_fileMons[file];
  540. }
  541. return fileMon;
  542. }
  543. FileMonitor AddFileMonitor(string file) {
  544. string path;
  545. FileMonitor fileMon;
  546. FindFileData ffd = null;
  547. int hr;
  548. if (String.IsNullOrEmpty(file)) {
  549. // add as the <ANY> file monitor
  550. fileMon = new FileMonitor(this, null, null, true, null, null);
  551. _anyFileMon = fileMon;
  552. }
  553. else {
  554. // Get the long and short name of the file
  555. path = Path.Combine(Directory, file);
  556. if (_isDirMonAppPathInternal) {
  557. hr = FindFileData.FindFile(path, Directory, out ffd);
  558. }
  559. else {
  560. hr = FindFileData.FindFile(path, out ffd);
  561. }
  562. if (hr == HResults.S_OK) {
  563. // Unless this is FileChangesMonitor._dirMonAppPathInternal,
  564. // don't monitor changes to a directory - this will not pickup changes to files in the directory.
  565. if (!_isDirMonAppPathInternal
  566. && (ffd.FileAttributesData.FileAttributes & FileAttributes.Directory) != 0) {
  567. throw FileChangesMonitor.CreateFileMonitoringException(HResults.E_INVALIDARG, path);
  568. }
  569. byte[] dacl = FileSecurity.GetDacl(path);
  570. fileMon = new FileMonitor(this, ffd.FileNameLong, ffd.FileNameShort, true, ffd.FileAttributesData, dacl);
  571. _fileMons.Add(ffd.FileNameLong, fileMon);
  572. // Update short name aliases to this file
  573. UpdateFileNameShort(fileMon, null, ffd.FileNameShort);
  574. }
  575. else if (hr == HResults.E_PATHNOTFOUND || hr == HResults.E_FILENOTFOUND) {
  576. // Don't allow possible short file names to be added as non-existant,
  577. // because it is impossible to track them if they are indeed a short name since
  578. // short file names may change.
  579. // FEATURE_PAL
  580. if (file.IndexOf('~') != -1) {
  581. throw FileChangesMonitor.CreateFileMonitoringException(HResults.E_INVALIDARG, path);
  582. }
  583. // Add as non-existent file
  584. fileMon = new FileMonitor(this, file, null, false, null, null);
  585. _fileMons.Add(file, fileMon);
  586. }
  587. else {
  588. throw FileChangesMonitor.CreateFileMonitoringException(hr, path);
  589. }
  590. }
  591. return fileMon;
  592. }
  593. //
  594. // Update short names of a file
  595. //
  596. void UpdateFileNameShort(FileMonitor fileMon, string oldFileNameShort, string newFileNameShort) {
  597. if (oldFileNameShort != null) {
  598. FileMonitor oldFileMonShort = (FileMonitor)_fileMons[oldFileNameShort];
  599. if (oldFileMonShort != null) {
  600. // The old filemonitor no longer has this short file name.
  601. // Update the monitor and _fileMons
  602. if (oldFileMonShort != fileMon) {
  603. oldFileMonShort.RemoveFileNameShort();
  604. }
  605. _fileMons.Remove(oldFileNameShort);
  606. _cShortNames--;
  607. }
  608. }
  609. if (newFileNameShort != null) {
  610. // Add the new short file name.
  611. _fileMons.Add(newFileNameShort, fileMon);
  612. _cShortNames++;
  613. }
  614. }
  615. void RemoveFileMonitor(FileMonitor fileMon) {
  616. if (fileMon == _anyFileMon) {
  617. _anyFileMon = null;
  618. }
  619. else {
  620. _fileMons.Remove(fileMon.FileNameLong);
  621. if (fileMon.FileNameShort != null) {
  622. _fileMons.Remove(fileMon.FileNameShort);
  623. _cShortNames--;
  624. }
  625. }
  626. HttpRuntime.FileChangesMonitor.RemoveAliases(fileMon);
  627. }
  628. int GetFileMonitorsCount() {
  629. int c = _fileMons.Count - _cShortNames;
  630. if (_anyFileMon != null) {
  631. c++;
  632. }
  633. return c;
  634. }
  635. // The 4.0 CAS changes made the AppDomain homogenous, so we need to assert
  636. // FileIOPermission. Currently this is only exposed publicly via CacheDependency, which
  637. // already does a PathDiscover check for public callers.
  638. [FileIOPermission(SecurityAction.Assert, Unrestricted = true)]
  639. internal FileMonitor StartMonitoringFileWithAssert(string file, FileChangeEventHandler callback, string alias) {
  640. FileMonitor fileMon = null;
  641. bool firstFileMonAdded = false;
  642. lock (this) {
  643. // Find existing file monitor
  644. fileMon = FindFileMonitor(file);
  645. if (fileMon == null) {
  646. // Add a new monitor
  647. fileMon = AddFileMonitor(file);
  648. if (GetFileMonitorsCount() == 1) {
  649. firstFileMonAdded = true;
  650. }
  651. }
  652. // Add callback to the file monitor
  653. fileMon.AddTarget(callback, alias, true);
  654. // Start directory monitoring when the first file gets added
  655. if (firstFileMonAdded) {
  656. StartMonitoring();
  657. }
  658. }
  659. return fileMon;
  660. }
  661. //
  662. // Request to stop monitoring a file.
  663. //
  664. internal void StopMonitoringFile(string file, object target) {
  665. FileMonitor fileMon;
  666. int numTargets;
  667. lock (this) {
  668. // Find existing file monitor
  669. fileMon = FindFileMonitor(file);
  670. if (fileMon != null) {
  671. numTargets = fileMon.RemoveTarget(target);
  672. if (numTargets == 0) {
  673. RemoveFileMonitor(fileMon);
  674. // last target for the file monitor gone
  675. // -- remove the file monitor
  676. if (GetFileMonitorsCount() == 0) {
  677. ((IDisposable)this).Dispose();
  678. }
  679. }
  680. }
  681. }
  682. #if DBG
  683. if (fileMon != null) {
  684. Debug.Dump("FileChangesMonitor", HttpRuntime.FileChangesMonitor);
  685. }
  686. #endif
  687. }
  688. internal bool GetFileAttributes(string file, out FileAttributesData fad) {
  689. FileMonitor fileMon = null;
  690. fad = null;
  691. lock (this) {
  692. // Find existing file monitor
  693. fileMon = FindFileMonitor(file);
  694. if (fileMon != null) {
  695. // Get the attributes
  696. fad = fileMon.Attributes;
  697. return true;
  698. }
  699. }
  700. return false;
  701. }
  702. //
  703. // Notes about file attributes:
  704. //
  705. // CreationTime is the time a file entry is added to a directory.
  706. // If file q1 is copied to q2, q2's creation time is updated if it is new to the directory,
  707. // else q2's old time is used.
  708. //
  709. // If a file is deleted, then added, its creation time is preserved from before the delete.
  710. //
  711. // LastWriteTime is the time a file was last written.
  712. // If file q1 is copied to q2, q2's lastWrite time is the same as q1.
  713. // Note that this implies that the LastWriteTime can be older than the LastCreationTime,
  714. // and that a copy of a file can result in the LastWriteTime being earlier than
  715. // its previous value.
  716. //
  717. // LastAccessTime is the time a file was last accessed, such as opened or written to.
  718. // Note that if the attributes of a file are changed, its LastAccessTime is not necessarily updated.
  719. //
  720. // If the FileSize, CreationTime, or LastWriteTime have changed, then we know that the
  721. // file has changed in a significant way, and that the LastAccessTime will be greater than
  722. // or equal to that time.
  723. //
  724. // If the FileSize, CreationTime, or LastWriteTime have not changed, then the file's
  725. // attributes may have changed without changing the LastAccessTime.
  726. //
  727. // Confirm that the changes occurred after we started monitoring,
  728. // to handle the case where:
  729. //
  730. // 1. User creates a file.
  731. // 2. User starts to monitor the file.
  732. // 3. Change notification is made of the original creation of the file.
  733. //
  734. // Note that we can only approximate when the last change occurred by
  735. // examining the LastAccessTime. The LastAccessTime will change if the
  736. // contents of a file (but not necessarily its attributes) change.
  737. // The drawback to using the LastAccessTime is that it will also be
  738. // updated when a file is read.
  739. //
  740. // Note that we cannot make this confirmation when only the file's attributes
  741. // or ACLs change, because changes to attributes and ACLs won't change the LastAccessTime.
  742. //
  743. bool IsChangeAfterStartMonitoring(FileAttributesData fad, FileMonitorTarget target, DateTime utcCompletion) {
  744. // If the LastAccessTime is more than 60 seconds before we
  745. // started monitoring, then the change likely did not update
  746. // the LastAccessTime correctly.
  747. if (fad.UtcLastAccessTime.AddSeconds(60) < target.UtcStartMonitoring) {
  748. #if DBG
  749. Debug.Trace("FileChangesMonitorIsChangeAfterStart", "LastAccessTime is more than 60 seconds before monitoring started.");
  750. #endif
  751. return true;
  752. }
  753. // Check if the notification of the change came after
  754. // we started monitoring.
  755. if (utcCompletion > target.UtcStartMonitoring) {
  756. #if DBG
  757. Debug.Trace("FileChangesMonitorIsChangeAfterStart", "Notification came after we started monitoring.");
  758. #endif
  759. return true;
  760. }
  761. // Make sure that the LastAccessTime is valid.
  762. // It must be more recent than the LastWriteTime.
  763. if (fad.UtcLastAccessTime < fad.UtcLastWriteTime) {
  764. #if DBG
  765. Debug.Trace("FileChangesMonitorIsChangeAfterStart", "UtcLastWriteTime is greater then UtcLastAccessTime.");
  766. #endif
  767. return true;
  768. }
  769. // If the LastAccessTime occurs exactly at midnight,
  770. // then the system is FAT32 and LastAccessTime is unusable.
  771. if (fad.UtcLastAccessTime.TimeOfDay == TimeSpan.Zero) {
  772. #if DBG
  773. Debug.Trace("FileChangesMonitorIsChangeAfterStart", "UtcLastAccessTime is midnight -- FAT32 likely.");
  774. #endif
  775. return true;
  776. }
  777. // Finally, compare LastAccessTime to the time we started monitoring.
  778. // If the time of the last access was before we started monitoring, then
  779. // we know a change did not occur to the file contents.
  780. if (fad.UtcLastAccessTime >= target.UtcStartMonitoring) {
  781. #if DBG
  782. Debug.Trace("FileChangesMonitorIsChangeAfterStart", "UtcLastAccessTime is greater than UtcStartMonitoring.");
  783. #endif
  784. return true;
  785. }
  786. #if DBG
  787. Debug.Trace("FileChangesMonitorIsChangeAfterStart", "Change is before start of monitoring. Data:\n FileAttributesData: \nUtcCreationTime: "
  788. + fad.UtcCreationTime + " UtcLastAccessTime: " + fad.UtcLastAccessTime + " UtcLastWriteTime: " + fad.UtcLastWriteTime + "\n FileMonitorTarget:\n UtcStartMonitoring: "
  789. + target.UtcStartMonitoring + "\nUtcCompletion: " + utcCompletion);
  790. #endif
  791. return false;
  792. }
  793. // If this is a special dirmon that monitors all files and subdirectories
  794. // beneath the vroot (enabled via FCNMode registry key), then
  795. // we need to special case how we lookup the FileMonitor. For example, nobody has called
  796. // StartMonitorFile for specific files in the App_LocalResources directory,
  797. // so we need to see if fileName is in App_LocalResources and then get the FileMonitor for
  798. // the directory.
  799. private bool GetFileMonitorForSpecialDirectory(string fileName, ref FileMonitor fileMon) {
  800. // fileName should not be in short form (8.3 format)...it was converted to long form in
  801. // DirMonCompletion::ProcessOneFileNotification
  802. // first search for match within s_dirsToMonitor
  803. for (int i = 0; i < FileChangesMonitor.s_dirsToMonitor.Length; i++) {
  804. if (StringUtil.StringStartsWithIgnoreCase(fileName, FileChangesMonitor.s_dirsToMonitor[i])) {
  805. fileMon = (FileMonitor)_fileMons[FileChangesMonitor.s_dirsToMonitor[i]];
  806. return fileMon != null;
  807. }
  808. }
  809. // if we did not find a match in s_dirsToMonitor, look for LocalResourcesDirectoryName anywhere within fileName
  810. int indexStart = fileName.IndexOf(HttpRuntime.LocalResourcesDirectoryName, StringComparison.OrdinalIgnoreCase);
  811. if (indexStart > -1) {
  812. int dirNameLength = indexStart + HttpRuntime.LocalResourcesDirectoryName.Length;
  813. // fileName should either end with LocalResourcesDirectoryName or include a trailing slash and more characters
  814. if (fileName.Length == dirNameLength || fileName[dirNameLength] == Path.DirectorySeparatorChar) {
  815. string dirName = fileName.Substring(0, dirNameLength);
  816. fileMon = (FileMonitor)_fileMons[dirName];
  817. return fileMon != null;
  818. }
  819. }
  820. return false;
  821. }
  822. //
  823. // Delegate callback from native code.
  824. //
  825. internal void OnFileChange(FileAction action, string fileName, DateTime utcCompletion) {
  826. //
  827. // Use try/catch to prevent runtime exceptions from propagating
  828. // into native code.
  829. //
  830. try {
  831. FileMonitor fileMon = null;
  832. ArrayList targets = null;
  833. int i, n;
  834. FileMonitorTarget target;
  835. ICollection col;
  836. string key;
  837. FileAttributesData fadOld = null;
  838. FileAttributesData fadNew = null;
  839. byte[] daclOld = null;
  840. byte[] daclNew = null;
  841. FileAction lastAction = FileAction.Error;
  842. DateTime utcLastCompletion = DateTime.MinValue;
  843. bool isSpecialDirectoryChange = false;
  844. #if DBG
  845. string reasonIgnore = string.Empty;
  846. string reasonFire = string.Empty;
  847. #endif
  848. // We've already stopped monitoring, but a change completion was
  849. // posted afterwards. Ignore it.
  850. if (_dirMonCompletion == null) {
  851. return;
  852. }
  853. lock (this) {
  854. if (_fileMons.Count > 0) {
  855. if (action == FileAction.Error || action == FileAction.Overwhelming) {
  856. // Overwhelming change -- notify all file monitors
  857. Debug.Assert(fileName == null, "fileName == null");
  858. Debug.Assert(action != FileAction.Overwhelming, "action != FileAction.Overwhelming");
  859. if (action == FileAction.Overwhelming) {
  860. //increase file notification buffer size, but only once per app instance
  861. if (Interlocked.Increment(ref s_notificationBufferSizeIncreased) == 1) {
  862. UnsafeNativeMethods.GrowFileNotificationBuffer( HttpRuntime.AppDomainAppId, _watchSubtree );
  863. }
  864. }
  865. // Get targets for all files
  866. targets = new ArrayList();
  867. foreach (DictionaryEntry d in _fileMons) {
  868. key = (string) d.Key;
  869. fileMon = (FileMonitor) d.Value;
  870. if (fileMon.FileNameLong == key) {
  871. fileMon.ResetCachedAttributes();
  872. fileMon.LastAction = action;
  873. fileMon.UtcLastCompletion = utcCompletion;
  874. col = fileMon.Targets;
  875. targets.AddRange(col);
  876. }
  877. }
  878. fileMon = null;
  879. }
  880. else {
  881. Debug.Assert((int) action >= 1 && fileName != null && fileName.Length > 0,
  882. "(int) action >= 1 && fileName != null && fileName.Length > 0");
  883. // Find the file monitor
  884. fileMon = (FileMonitor)_fileMons[fileName];
  885. if (_isDirMonAppPathInternal && fileMon == null) {
  886. isSpecialDirectoryChange = GetFileMonitorForSpecialDirectory(fileName, ref fileMon);
  887. }
  888. if (fileMon != null) {
  889. // Get the targets
  890. col = fileMon.Targets;
  891. targets = new ArrayList(col);
  892. fadOld = fileMon.Attributes;
  893. daclOld = fileMon.Dacl;
  894. lastAction = fileMon.LastAction;
  895. utcLastCompletion = fileMon.UtcLastCompletion;
  896. fileMon.LastAction = action;
  897. fileMon.UtcLastCompletion = utcCompletion;
  898. if (action == FileAction.Removed || action == FileAction.RenamedOldName) {
  899. // File not longer exists.
  900. fileMon.MakeExtinct();
  901. }
  902. else if (fileMon.Exists) {
  903. // We only need to update the attributes if this is
  904. // a different completion, as we retreive the attributes
  905. // after the completion is received.
  906. if (utcLastCompletion != utcCompletion) {
  907. fileMon.UpdateCachedAttributes();
  908. }
  909. }
  910. else {
  911. // File now exists - update short name and attributes.
  912. FindFileData ffd = null;
  913. string path = Path.Combine(Directory, fileMon.FileNameLong);
  914. int hr;
  915. if (_isDirMonAppPathInternal) {
  916. hr = FindFileData.FindFile(path, Directory, out ffd);
  917. }
  918. else {
  919. hr = FindFileData.FindFile(path, out ffd);
  920. }
  921. if (hr == HResults.S_OK) {
  922. Debug.Assert(StringUtil.EqualsIgnoreCase(fileMon.FileNameLong, ffd.FileNameLong),
  923. "StringUtil.EqualsIgnoreCase(fileMon.FileNameLong, ffd.FileNameLong)");
  924. string oldFileNameShort = fileMon.FileNameShort;
  925. byte[] dacl = FileSecurity.GetDacl(path);
  926. fileMon.MakeExist(ffd, dacl);
  927. UpdateFileNameShort(fileMon, oldFileNameShort, ffd.FileNameShort);
  928. }
  929. }
  930. fadNew = fileMon.Attributes;
  931. daclNew = fileMon.Dacl;
  932. }
  933. }
  934. }
  935. // Notify the delegate waiting for any changes
  936. if (_anyFileMon != null) {
  937. col = _anyFileMon.Targets;
  938. if (targets != null) {
  939. targets.AddRange(col);
  940. }
  941. else {
  942. targets = new ArrayList(col);
  943. }
  944. }
  945. if (action == FileAction.Error) {
  946. // Stop monitoring.
  947. ((IDisposable)this).Dispose();
  948. }
  949. }
  950. // Ignore Modified action for directories (VSWhidbey 295597)
  951. bool ignoreThisChangeNotification = false;
  952. if (!isSpecialDirectoryChange && fileName != null && action == FileAction.Modified) {
  953. // check if the file is a directory (reuse attributes if already obtained)
  954. FileAttributesData fad = fadNew;
  955. if (fad == null) {
  956. string path = Path.Combine(Directory, fileName);
  957. FileAttributesData.GetFileAttributes(path, out fad);
  958. }
  959. if (fad != null && ((fad.FileAttributes & FileAttributes.Directory) != 0)) {
  960. // ignore if directory
  961. ignoreThisChangeNotification = true;
  962. }
  963. }
  964. // Dev10 440497: Don't unload AppDomain when a folder is deleted or renamed, unless we're monitoring files in it
  965. if (_ignoreSubdirChange && (action == FileAction.Removed || action == FileAction.RenamedOldName) && fileName != null) {
  966. string fullPath = Path.Combine(Directory, fileName);
  967. if (!HttpRuntime.FileChangesMonitor.IsDirNameMonitored(fullPath, fileName)) {
  968. #if DBG
  969. Debug.Trace("FileChangesMonitorIgnoreSubdirChange",