LinuxCommunicator/HostsFile/HostsFileManager.cs (154 lines of code) (raw):
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Diagnostics;
using System.Threading;
namespace Microsoft.Hpc.Communicators.LinuxCommunicator.HostsFile
{
/// <summary>
/// This class abstracts a hosts file so that is can be managed by HPC.
/// </summary>
public class HostsFileManager : IDisposable
{
/// <summary>
/// The string placed in the comment field of an entry managed
/// by the cluster.
/// </summary>
public const string ManagedEntryKey = "HPC";
/// <summary>
/// The parameter name used to indicate whether the hosts file is HPC managed.
/// </summary>
public const string ManageFileParameter = "ManageFile";
/// <summary>
/// The interval to reload hosts file
/// </summary>
private const int ReloadInterval = 1000 * 60;
/// <summary>
/// Regular expression for a comment line in the text file
/// </summary>
private static readonly Regex Comment = new Regex(@"^#(?<1>.*)$");
/// <summary>
/// Regular expression for a parameter value in the comments section.
/// </summary>
private static readonly Regex CommentParameter = new Regex(@"^#\s*(?<parameter>[\w\p{Lm}\p{Nl}\p{Cf}\p{Mn}\p{Mc}\.]+)\s*=\s*(?<value>[\w\p{Lm}\p{Nl}\p{Cf}\p{Mn}\p{Mc}\.]+)");
/// <summary>
/// Regular expression for a ip entry in the text file.
/// </summary>
private static readonly Regex IpEntry = new Regex(@"^(?<ip>[0-9\.]+)\s+(?<dnsName>[^\s#]+)(\s+#(?<comment>.*))?");
/// <summary>
/// The path to the hosts file
/// </summary>
private string filepath;
/// <summary>
/// The timer to reload hosts file
/// </summary>
private Timer reloadTimer;
/// <summary>
/// The last modified date of the current loaded hosts file
/// </summary>
private DateTime lastModified = DateTime.MinValue;
/// <summary>
/// The unique update ID
/// </summary>
public Guid UpdateId
{
get;
private set;
}
/// <summary>
/// This constructor initializes the parser on a specific hosts file
/// </summary>
public HostsFileManager()
{
this.filepath = Path.Combine(Environment.SystemDirectory, @"drivers\etc\hosts");
this.ManagedEntries = new List<HostEntry>();
this.reloadTimer = new Timer(ReloadTimerEvent, null, 0, Timeout.Infinite);
}
/// <summary>
/// Gets the list of HPC managed entries in the host file.
/// </summary>
public List<HostEntry> ManagedEntries
{
get;
private set;
}
/// <summary>
/// Gets or sets a flag indicating whether the file is HPC managed
/// </summary>
public bool ManagedByHPC
{
get;
private set;
}
/// <summary>
/// Loads the contents of an existing host file. Will clear
/// the current configuration of this instance.
/// </summary>
/// <param name="path"></param>
public void ReloadTimerEvent(object param)
{
try
{
FileInfo fileInfo = new FileInfo(this.filepath);
if (!fileInfo.Exists)
{
LinuxCommunicator.Instance?.Tracer?.TraceInfo("[HostsFileManager] The hosts file doesn't exists: {0}", this.filepath);
return;
}
if (fileInfo.LastWriteTimeUtc <= this.lastModified)
{
LinuxCommunicator.Instance?.Tracer?.TraceInfo("[HostsFileManager] The hosts file isn't changed since last load");
return;
}
bool manageByHPC = false;
Dictionary<string, HostEntry> newEntries = new Dictionary<string, HostEntry>();
foreach (var line in File.ReadAllLines(this.filepath))
{
Match commentMatch = HostsFileManager.Comment.Match(line);
if (commentMatch.Success)
{
Match commentParameterMatch = HostsFileManager.CommentParameter.Match(line);
if (commentParameterMatch.Success && string.Equals(commentParameterMatch.Groups["parameter"].ToString(), ManageFileParameter, StringComparison.OrdinalIgnoreCase))
{
if (string.Equals(commentParameterMatch.Groups["value"].Value, "true", StringComparison.OrdinalIgnoreCase))
{
manageByHPC = true;
}
}
continue;
}
Match ipEntryMatch = HostsFileManager.IpEntry.Match(line);
HostEntry entry;
if (ipEntryMatch.Success &&
manageByHPC &&
ipEntryMatch.Groups["comment"].Value.Equals(HostsFileManager.ManagedEntryKey, StringComparison.OrdinalIgnoreCase) &&
HostEntry.TryCreate(ipEntryMatch.Groups["dnsName"].Value, ipEntryMatch.Groups["ip"].Value, out entry))
{
newEntries[entry.Name] = entry;
}
}
if (manageByHPC)
{
if (newEntries.Count != this.ManagedEntries.Count || !(new HashSet<HostEntry>(this.ManagedEntries)).SetEquals(new HashSet<HostEntry>(newEntries.Values)))
{
// Put the <NetworkType>.<NodeName> entries at the end of the list
var newHostList = newEntries.Values.Where(entry => !entry.Name.Contains('.')).ToList();
newHostList.AddRange(newEntries.Values.Where(entry => entry.Name.Contains('.')));
this.ManagedEntries = newHostList;
this.UpdateId = Guid.NewGuid();
LinuxCommunicator.Instance?.Tracer?.TraceInfo("[HostsFileManager] The managed host entries updated, current update Id is {0}", this.UpdateId);
}
else
{
LinuxCommunicator.Instance?.Tracer?.TraceInfo("[HostsFileManager] No update to HPC managed host entries, current update Id is {0}", this.UpdateId);
}
}
else
{
LinuxCommunicator.Instance?.Tracer?.TraceWarning("[HostsFileManager] Hosts file was not managed by HPC");
this.ManagedEntries.Clear();
}
this.ManagedByHPC = manageByHPC;
this.lastModified = fileInfo.LastWriteTimeUtc;
}
catch (Exception e)
{
LinuxCommunicator.Instance?.Tracer?.TraceWarning("[HostsFileManager] Failed to reload host file: {0}", e);
}
finally
{
try
{
this.reloadTimer.Change(ReloadInterval, Timeout.Infinite);
}
catch (Exception te)
{
LinuxCommunicator.Instance?.Tracer?.TraceWarning("[HostsFileManager] Failed to restart reload timer: {0}", te);
}
}
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool isDisposing)
{
if (isDisposing)
{
if (this.reloadTimer != null)
{
this.reloadTimer.Dispose();
}
}
}
}
}