e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
169 lines
7.1 KiB
C#
169 lines
7.1 KiB
C#
// <copyright file="HostFileChangeMonitor.cs" company="Microsoft">
|
|
// Copyright (c) 2009 Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
using System;
|
|
using System.Runtime.Caching.Hosting;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Runtime.Caching.Resources;
|
|
using System.Globalization;
|
|
using System.Security;
|
|
using System.Security.Permissions;
|
|
using System.Text;
|
|
using System.Threading;
|
|
|
|
namespace System.Runtime.Caching {
|
|
public sealed class HostFileChangeMonitor : FileChangeMonitor {
|
|
private const int MAX_CHAR_COUNT_OF_LONG_CONVERTED_TO_HEXADECIMAL_STRING = 16;
|
|
private static IFileChangeNotificationSystem s_fcn;
|
|
private readonly ReadOnlyCollection<String> _filePaths;
|
|
private String _uniqueId;
|
|
private Object _fcnState;
|
|
private DateTimeOffset _lastModified;
|
|
|
|
private HostFileChangeMonitor() { } // hide default .ctor
|
|
|
|
private void InitDisposableMembers() {
|
|
bool dispose = true;
|
|
try {
|
|
string uniqueId = null;
|
|
if (_filePaths.Count == 1) {
|
|
string path = _filePaths[0];
|
|
DateTimeOffset lastWrite;
|
|
long fileSize;
|
|
s_fcn.StartMonitoring(path, new OnChangedCallback(OnChanged), out _fcnState, out lastWrite, out fileSize);
|
|
uniqueId = path + lastWrite.UtcDateTime.Ticks.ToString("X", CultureInfo.InvariantCulture) + fileSize.ToString("X", CultureInfo.InvariantCulture);
|
|
_lastModified = lastWrite;
|
|
}
|
|
else {
|
|
int capacity = 0;
|
|
foreach (string path in _filePaths) {
|
|
capacity += path.Length + (2 * MAX_CHAR_COUNT_OF_LONG_CONVERTED_TO_HEXADECIMAL_STRING);
|
|
}
|
|
Hashtable fcnState = new Hashtable(_filePaths.Count);
|
|
_fcnState = fcnState;
|
|
StringBuilder sb = new StringBuilder(capacity);
|
|
foreach (string path in _filePaths) {
|
|
if (fcnState.Contains(path)) {
|
|
continue;
|
|
}
|
|
DateTimeOffset lastWrite;
|
|
long fileSize;
|
|
object state;
|
|
s_fcn.StartMonitoring(path, new OnChangedCallback(OnChanged), out state, out lastWrite, out fileSize);
|
|
fcnState[path] = state;
|
|
sb.Append(path);
|
|
sb.Append(lastWrite.UtcDateTime.Ticks.ToString("X", CultureInfo.InvariantCulture));
|
|
sb.Append(fileSize.ToString("X", CultureInfo.InvariantCulture));
|
|
if (lastWrite > _lastModified) {
|
|
_lastModified = lastWrite;
|
|
}
|
|
}
|
|
uniqueId = sb.ToString();
|
|
}
|
|
_uniqueId = uniqueId;
|
|
dispose = false;
|
|
}
|
|
finally {
|
|
InitializationComplete();
|
|
if (dispose) {
|
|
Dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
[SecuritySafeCritical]
|
|
[PermissionSet(SecurityAction.Assert, Unrestricted = true)]
|
|
[SuppressMessage("Microsoft.Security", "CA2106:SecureAsserts", Justification = "Grandfathered suppression from original caching code checkin")]
|
|
private static void InitFCN() {
|
|
if (s_fcn == null) {
|
|
IFileChangeNotificationSystem fcn = null;
|
|
IServiceProvider host = ObjectCache.Host;
|
|
if (host != null) {
|
|
fcn = host.GetService(typeof(IFileChangeNotificationSystem)) as IFileChangeNotificationSystem;
|
|
}
|
|
if (fcn == null) {
|
|
fcn = new FileChangeNotificationSystem();
|
|
}
|
|
Interlocked.CompareExchange(ref s_fcn, fcn, null);
|
|
}
|
|
}
|
|
|
|
//
|
|
// protected members
|
|
//
|
|
|
|
protected override void Dispose(bool disposing) {
|
|
if (disposing && s_fcn != null) {
|
|
if (_filePaths != null && _fcnState != null) {
|
|
if (_filePaths.Count > 1) {
|
|
Hashtable fcnState = _fcnState as Hashtable;
|
|
foreach (string path in _filePaths) {
|
|
if (path != null) {
|
|
object state = fcnState[path];
|
|
if (state != null) {
|
|
s_fcn.StopMonitoring(path, state);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
string path = _filePaths[0];
|
|
if (path != null && _fcnState != null) {
|
|
s_fcn.StopMonitoring(path, _fcnState);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// public and internal members
|
|
//
|
|
|
|
public override ReadOnlyCollection<String> FilePaths { get { return _filePaths; } }
|
|
public override String UniqueId { get { return _uniqueId; } }
|
|
public override DateTimeOffset LastModified { get { return _lastModified; } }
|
|
|
|
public HostFileChangeMonitor(IList<String> filePaths) {
|
|
if (filePaths == null) {
|
|
throw new ArgumentNullException("filePaths");
|
|
}
|
|
if (filePaths.Count == 0) {
|
|
throw new ArgumentException(RH.Format(R.Empty_collection, "filePaths"));
|
|
}
|
|
|
|
// *SECURITY* - filePaths is untrusted and should not be consumed outside of the sanitization method.
|
|
_filePaths = SanitizeFilePathsList(filePaths);
|
|
|
|
InitFCN();
|
|
InitDisposableMembers();
|
|
}
|
|
|
|
[SecuritySafeCritical]
|
|
private static ReadOnlyCollection<string> SanitizeFilePathsList(IList<string> filePaths) {
|
|
List<string> newList = new List<string>(filePaths.Count);
|
|
|
|
foreach (string path in filePaths) {
|
|
if (String.IsNullOrEmpty(path)) {
|
|
throw new ArgumentException(RH.Format(R.Collection_contains_null_or_empty_string, "filePaths"));
|
|
}
|
|
else {
|
|
// DevDiv #269534: When we use a user-provided string in the constructor to this FileIOPermission and demand the permission,
|
|
// we need to be certain that we're adding *exactly that string* to the new list we're generating. The original code tried to
|
|
// optimize by checking all of the permissions upfront then doing a List<T>.AddRange at the end, but this opened the method
|
|
// to a TOCTOU attack since a malicious user could have modified the original IList<T> between the two calls.
|
|
new FileIOPermission(FileIOPermissionAccess.PathDiscovery, path).Demand();
|
|
newList.Add(path);
|
|
}
|
|
}
|
|
|
|
return newList.AsReadOnly();
|
|
}
|
|
|
|
}
|
|
}
|