Permalink
Fetching contributors…
Cannot retrieve contributors at this time
1745 lines (1486 sloc) 75.7 KB
//------------------------------------------------------------------------------
// <copyright file="EventLogInternal.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
//#define RETRY_ON_ALL_ERRORS
/*
* EventLogInternal contains most of the logic for interacting with the Windows Event Log.
* The reason for this class existing (instead of the logic being in EventLog itself) is
* that we'd like to be able to have the invariant that the Source, MachineName and Log Name
* don't change across the lifetime of an event log object, but we exposed public setters for
* these properties. EventLog holds a reference to an EventLogInternal instance, plumbs all
* calls to it and replaces it when any of these properites change.
*
* Note that EventLogInternal also holds a reference back to the EventLog instnace that is
* exposing it so it is not prematurely collected.
*/
namespace System.Diagnostics {
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Diagnostics;
using System;
using Microsoft.Win32;
using Microsoft.Win32.SafeHandles;
using System.IO;
using System.Collections;
using System.Collections.Specialized;
using System.Globalization;
using System.ComponentModel.Design;
using System.Security;
using System.Security.Permissions;
using System.Reflection;
using System.Runtime.Versioning;
using System.Runtime.CompilerServices;
using System.Diagnostics.CodeAnalysis;
/// <devdoc>
/// <para>
/// Provides interaction with Windows 2000 event logs.
/// </para>
/// </devdoc>
internal class EventLogInternal : IDisposable, ISupportInitialize {
// a collection over all our entries. Since the class holds no state, we
// can just hand the same instance out every time.
private EventLogEntryCollection entriesCollection;
// the name of the log we're reading from or writing to
internal string logName;
// used in monitoring for event postings.
private int lastSeenCount;
// holds the machine we're on, or null if it's the local machine
internal readonly string machineName;
// the delegate to call when an event arrives
internal EntryWrittenEventHandler onEntryWrittenHandler;
// holds onto the handle for reading
private SafeEventLogReadHandle readHandle;
// the source name - used only when writing
internal readonly string sourceName;
// holds onto the handle for writing
private SafeEventLogWriteHandle writeHandle;
private string logDisplayName;
// cache system state variables
// the initial size of the buffer (it can be made larger if necessary)
private const int BUF_SIZE = 40000;
// the number of bytes in the cache that belong to entries (not necessarily
// the same as BUF_SIZE, because the cache only holds whole entries)
private int bytesCached;
// the actual cache buffer
private byte[] cache;
// the number of the entry at the beginning of the cache
private int firstCachedEntry = -1;
// the number of the entry that we got out of the cache most recently
private int lastSeenEntry;
// where that entry was
private int lastSeenPos;
//support for threadpool based deferred execution
private ISynchronizeInvoke synchronizingObject;
// the EventLog object that publicly exposes this instance.
private readonly EventLog parent;
private const string EventLogKey = "SYSTEM\\CurrentControlSet\\Services\\EventLog";
internal const string DllName = "EventLogMessages.dll";
private const string eventLogMutexName = "netfxeventlog.1.0";
private const int SecondsPerDay = 60 * 60 * 24;
private const int DefaultMaxSize = 512*1024;
private const int DefaultRetention = 7*SecondsPerDay;
private const int Flag_notifying = 0x1; // keeps track of whether we're notifying our listeners - to prevent double notifications
private const int Flag_forwards = 0x2; // whether the cache contains entries in forwards order (true) or backwards (false)
private const int Flag_initializing = 0x4;
internal const int Flag_monitoring = 0x8;
private const int Flag_registeredAsListener = 0x10;
private const int Flag_writeGranted = 0x20;
private const int Flag_disposed = 0x100;
private const int Flag_sourceVerified= 0x200;
private BitVector32 boolFlags = new BitVector32();
private Hashtable messageLibraries;
private readonly static Hashtable listenerInfos = new Hashtable(StringComparer.OrdinalIgnoreCase);
private Object m_InstanceLockObject;
private Object InstanceLockObject {
get {
if (m_InstanceLockObject == null) {
Object o = new Object();
Interlocked.CompareExchange(ref m_InstanceLockObject, o, null);
}
return m_InstanceLockObject;
}
}
private static Object s_InternalSyncObject;
private static Object InternalSyncObject {
get {
if (s_InternalSyncObject == null) {
Object o = new Object();
Interlocked.CompareExchange(ref s_InternalSyncObject, o, null);
}
return s_InternalSyncObject;
}
}
/// <devdoc>
/// <para>
/// Initializes a new instance of the <see cref='System.Diagnostics.EventLog'/>
/// class.
/// </para>
/// </devdoc>
public EventLogInternal() : this("", ".", "", null) {
}
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public EventLogInternal(string logName) : this(logName, ".", "", null) {
}
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public EventLogInternal(string logName, string machineName) : this(logName, machineName, "", null) {
}
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public EventLogInternal(string logName, string machineName, string source) : this(logName, machineName, source, null) {
}
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
[SuppressMessage("Microsoft.Security", "CA2103:ReviewImperativeSecurity", Justification = "Microsoft: Safe, oldLog.machineName doesn't change")]
public EventLogInternal(string logName, string machineName, string source, EventLog parent) {
//look out for invalid log names
if (logName == null)
throw new ArgumentNullException("logName");
if (!ValidLogName(logName, true))
throw new ArgumentException(SR.GetString(SR.BadLogName));
if (!SyntaxCheck.CheckMachineName(machineName))
throw new ArgumentException(SR.GetString(SR.InvalidParameter, "machineName", machineName));
EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Write, machineName);
permission.Demand();
this.machineName = machineName;
this.logName = logName;
this.sourceName = source;
readHandle = null;
writeHandle = null;
boolFlags[Flag_forwards] = true;
this.parent = parent;
}
/// <devdoc>
/// <para>
/// Gets the contents of the event log.
/// </para>
/// </devdoc>
public EventLogEntryCollection Entries {
get {
string currentMachineName = this.machineName;
EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Administer, currentMachineName);
permission.Demand();
if (entriesCollection == null)
entriesCollection = new EventLogEntryCollection(this);
return entriesCollection;
}
}
/// <devdoc>
/// Gets the number of entries in the log
/// </devdoc>
internal int EntryCount {
get {
if (!IsOpenForRead)
OpenForRead(this.machineName);
int count;
bool success = UnsafeNativeMethods.GetNumberOfEventLogRecords(readHandle, out count);
if (!success)
throw SharedUtils.CreateSafeWin32Exception();
return count;
}
}
/// <devdoc>
/// Determines whether the event log is open in either read or write access
/// </devdoc>
private bool IsOpen {
get {
return readHandle != null || writeHandle != null;
}
}
/// <devdoc>
/// Determines whether the event log is open with read access
/// </devdoc>
private bool IsOpenForRead {
get {
return readHandle != null;
}
}
/// <devdoc>
/// Determines whether the event log is open with write access.
/// </devdoc>
private bool IsOpenForWrite {
get {
return writeHandle != null;
}
}
/// <devdoc>
/// <para>
/// </para>
/// </devdoc>
public string LogDisplayName {
[ResourceExposure(ResourceScope.Machine)]
[ResourceConsumption(ResourceScope.Machine)]
get {
if (logDisplayName != null)
return logDisplayName;
string currentMachineName = this.machineName;
if (GetLogName(currentMachineName) != null) {
EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Administer, currentMachineName);
permission.Demand();
//Check environment before looking at the registry
SharedUtils.CheckEnvironment();
//SECREVIEW: Note that EventLogPermission is just demmanded above
PermissionSet permissionSet = EventLog._UnsafeGetAssertPermSet();
permissionSet.Assert();
RegistryKey logkey = null;
try {
// we figure out what logs are on the machine by looking in the registry.
logkey = GetLogRegKey(currentMachineName, false);
if (logkey == null)
throw new InvalidOperationException(SR.GetString(SR.MissingLog, GetLogName(currentMachineName), currentMachineName));
string resourceDll = (string)logkey.GetValue("DisplayNameFile");
if (resourceDll == null)
logDisplayName = GetLogName(currentMachineName);
else {
int resourceId = (int)logkey.GetValue("DisplayNameID");
logDisplayName = FormatMessageWrapper(resourceDll, (uint) resourceId, null);
if (logDisplayName == null)
logDisplayName = GetLogName(currentMachineName);
}
}
finally {
if (logkey != null) logkey.Close();
// Revert registry and environment permission asserts
CodeAccessPermission.RevertAssert();
}
}
return logDisplayName;
}
}
/// <devdoc>
/// <para>
/// Gets the name of the log to read from and write to.
/// </para>
/// </devdoc>
public string Log {
get {
string currentMachineName = this.machineName;
if (logName == null || logName.Length == 0) {
EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Administer, currentMachineName);
permission.Demand();
}
return GetLogName(currentMachineName);
}
}
[SuppressMessage("Microsoft.Security", "CA2103:ReviewImperativeSecurity", Justification = "Microsoft: Safe, machineName doesn't change")]
private string GetLogName(string currentMachineName)
{
if ((logName == null || logName.Length == 0) && sourceName != null && sourceName.Length!=0) {
// they've told us a source, but they haven't told us a log name.
// try to deduce the log name from the source name.
EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Write, currentMachineName);
permission.Demand();
logName = EventLog._InternalLogNameFromSourceName(sourceName, currentMachineName);
}
return logName;
}
/// <devdoc>
/// <para>
/// Gets the name of the computer on which to read or write events.
/// </para>
/// </devdoc>
public string MachineName {
get {
string currentMachineName = this.machineName;
EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Write, currentMachineName);
permission.Demand();
return currentMachineName;
}
}
[ComVisible(false)]
[SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "Microsoft: MaximumKilobytes is the name of this property.")]
public long MaximumKilobytes {
get {
string currentMachineName = this.machineName;
EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Administer, currentMachineName);
permission.Demand();
object val = GetLogRegValue(currentMachineName, "MaxSize");
if (val != null) {
int intval = (int) val; // cast to an int first to unbox
return ((uint)intval) / 1024; // then convert to kilobytes
}
// 512k is the default value
return 0x200;
}
[ResourceExposure(ResourceScope.None)]
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
set {
string currentMachineName = this.machineName;
EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Administer, currentMachineName);
permission.Demand();
// valid range is 64 KB to 4 GB
if (value < 64 || value > 0x3FFFC0 || value % 64 != 0)
throw new ArgumentOutOfRangeException("MaximumKilobytes", SR.GetString(SR.MaximumKilobytesOutOfRange));
PermissionSet permissionSet = EventLog._UnsafeGetAssertPermSet();
permissionSet.Assert();
long regvalue = value * 1024; // convert to bytes
int i = unchecked((int)regvalue);
using (RegistryKey logkey = GetLogRegKey(currentMachineName, true))
logkey.SetValue("MaxSize", i, RegistryValueKind.DWord);
}
}
internal Hashtable MessageLibraries {
get {
if (messageLibraries == null)
messageLibraries = new Hashtable(StringComparer.OrdinalIgnoreCase);
return messageLibraries;
}
}
[ComVisible(false)]
public OverflowAction OverflowAction {
get {
string currentMachineName = this.machineName;
EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Administer, currentMachineName);
permission.Demand();
object retentionobj = GetLogRegValue(currentMachineName, "Retention");
if (retentionobj != null) {
int retention = (int) retentionobj;
if (retention == 0)
return OverflowAction.OverwriteAsNeeded;
else if (retention == -1)
return OverflowAction.DoNotOverwrite;
else
return OverflowAction.OverwriteOlder;
}
// default value as listed in MSDN
return OverflowAction.OverwriteOlder;
}
}
[ComVisible(false)]
public int MinimumRetentionDays {
get {
string currentMachineName = this.machineName;
EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Administer, currentMachineName);
permission.Demand();
object retentionobj = GetLogRegValue(currentMachineName, "Retention");
if (retentionobj != null) {
int retention = (int) retentionobj;
if (retention == 0 || retention == -1)
return retention;
else
return (int) (((double) retention) / SecondsPerDay);
}
return 7;
}
}
/// <devdoc>
/// </devdoc>
public bool EnableRaisingEvents {
get {
string currentMachineName = this.machineName;
EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Write, currentMachineName);
permission.Demand();
return boolFlags[Flag_monitoring];
}
set {
string currentMachineName = this.machineName;
EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Write, currentMachineName);
permission.Demand();
if (parent.ComponentDesignMode)
this.boolFlags[Flag_monitoring] = value;
else {
if (value)
StartRaisingEvents(currentMachineName, GetLogName(currentMachineName));
else
StopRaisingEvents(/*currentMachineName,*/ GetLogName(currentMachineName));
}
}
}
private int OldestEntryNumber {
get {
if (!IsOpenForRead)
OpenForRead(this.machineName);
int num;
bool success = UnsafeNativeMethods.GetOldestEventLogRecord(readHandle, out num);
if (!success)
throw SharedUtils.CreateSafeWin32Exception();
// When the event log is empty, GetOldestEventLogRecord returns 0.
// But then after an entry is written, it returns 1. We need to go from
// the last num to the current.
if (num == 0)
num = 1;
return num;
}
}
internal SafeEventLogReadHandle ReadHandle {
get {
if (!IsOpenForRead)
OpenForRead(this.machineName);
return readHandle;
}
}
/// <devdoc>
/// <para>
/// Represents the object used to marshal the event handler
/// calls issued as a result of an <see cref='System.Diagnostics.EventLog'/>
/// change.
/// </para>
/// </devdoc>
public ISynchronizeInvoke SynchronizingObject {
[HostProtection(Synchronization=true)]
get {
string currentMachineName = this.machineName;
EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Write, currentMachineName);
permission.Demand();
if (this.synchronizingObject == null && parent.ComponentDesignMode) {
IDesignerHost host = (IDesignerHost) parent.ComponentGetService(typeof(IDesignerHost));
if (host != null) {
object baseComponent = host.RootComponent;
if (baseComponent != null && baseComponent is ISynchronizeInvoke)
this.synchronizingObject = (ISynchronizeInvoke)baseComponent;
}
}
return this.synchronizingObject;
}
set {
this.synchronizingObject = value;
}
}
/// <devdoc>
/// <para>
/// Gets or
/// sets the application name (source name) to register and use when writing to the event log.
/// </para>
/// </devdoc>
public string Source {
get {
string currentMachineName = this.machineName;
EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Write, currentMachineName);
permission.Demand();
return sourceName;
}
}
[HostProtection(Synchronization=true)]
[ResourceExposure(ResourceScope.None)]
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
private static void AddListenerComponent(EventLogInternal component, string compMachineName, string compLogName) {
lock (InternalSyncObject) {
Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::AddListenerComponent(" + compLogName + ")");
LogListeningInfo info = (LogListeningInfo) listenerInfos[compLogName];
if (info != null) {
Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::AddListenerComponent: listener already active.");
info.listeningComponents.Add(component);
return;
}
info = new LogListeningInfo();
info.listeningComponents.Add(component);
info.handleOwner = new EventLogInternal(compLogName, compMachineName);
// tell the event log system about it
info.waitHandle = new AutoResetEvent(false);
bool success = UnsafeNativeMethods.NotifyChangeEventLog(info.handleOwner.ReadHandle, info.waitHandle.SafeWaitHandle);
if (!success)
throw new InvalidOperationException(SR.GetString(SR.CantMonitorEventLog), SharedUtils.CreateSafeWin32Exception());
info.registeredWaitHandle = ThreadPool.RegisterWaitForSingleObject(info.waitHandle, new WaitOrTimerCallback(StaticCompletionCallback), info, -1, false);
listenerInfos[compLogName] = info;
}
}
/// <devdoc>
/// <para>
/// Occurs when an entry is written to the event log.
/// </para>
/// </devdoc>
public event EntryWrittenEventHandler EntryWritten {
add {
string currentMachineName = this.machineName;
EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Administer, currentMachineName);
permission.Demand();
onEntryWrittenHandler += value;
}
remove {
string currentMachineName = this.machineName;
EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Administer, currentMachineName);
permission.Demand();
onEntryWrittenHandler -= value;
}
}
/// <devdoc>
/// </devdoc>
public void BeginInit() {
string currentMachineName = this.machineName;
EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Write, currentMachineName);
permission.Demand();
if (boolFlags[Flag_initializing]) throw new InvalidOperationException(SR.GetString(SR.InitTwice));
boolFlags[Flag_initializing] = true;
if (boolFlags[Flag_monitoring])
StopListening(GetLogName(currentMachineName));
}
/// <devdoc>
/// <para>
/// Clears
/// the event log by removing all entries from it.
/// </para>
/// </devdoc>
[ResourceExposure(ResourceScope.Machine)] // Should anyone ever call this, other than an event log viewer?
[ResourceConsumption(ResourceScope.Machine)]
public void Clear() {
string currentMachineName = this.machineName;
EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Administer, currentMachineName);
permission.Demand();
if (!IsOpenForRead)
OpenForRead(currentMachineName);
bool success = UnsafeNativeMethods.ClearEventLog(readHandle, NativeMethods.NullHandleRef);
if (!success) {
// Ignore file not found errors. ClearEventLog seems to try to delete the file where the event log is
// stored. If it can't find it, it gives an error.
int error = Marshal.GetLastWin32Error();
if (error != NativeMethods.ERROR_FILE_NOT_FOUND)
throw SharedUtils.CreateSafeWin32Exception();
}
// now that we've cleared the event log, we need to re-open our handles, because
// the internal state of the event log has changed.
Reset(currentMachineName);
}
/// <devdoc>
/// <para>
/// Closes the event log and releases read and write handles.
/// </para>
/// </devdoc>
[ResourceExposure(ResourceScope.None)]
public void Close() {
Close(this.machineName);
}
[SuppressMessage("Microsoft.Security", "CA2103:ReviewImperativeSecurity", Justification = "Microsoft: Safe, currentMachineName doesn't change")]
private void Close(string currentMachineName) {
EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Write, currentMachineName);
permission.Demand();
Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::Close");
//Trace("Close", "Closing the event log");
if (readHandle != null) {
try {
readHandle.Close();
}
catch (IOException) {
throw SharedUtils.CreateSafeWin32Exception();
}
readHandle = null;
//Trace("Close", "Closed read handle");
Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::Close: closed read handle");
}
if (writeHandle != null) {
try {
writeHandle.Close();
}
catch (IOException) {
throw SharedUtils.CreateSafeWin32Exception();
}
writeHandle = null;
//Trace("Close", "Closed write handle");
Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::Close: closed write handle");
}
if (boolFlags[Flag_monitoring])
StopRaisingEvents(/*currentMachineName,*/ GetLogName(currentMachineName));
if (messageLibraries != null) {
foreach (SafeLibraryHandle handle in messageLibraries.Values)
handle.Close();
messageLibraries = null;
}
boolFlags[Flag_sourceVerified] = false;
}
/// <internalonly/>
/// <devdoc>
/// Called when the threadpool is ready for us to handle a status change.
/// </devdoc>
private void CompletionCallback(object context) {
if (boolFlags[Flag_disposed]) {
// This object has been disposed previously, ignore firing the event.
return;
}
Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::CompletionStatusChanged: starting at " + lastSeenCount.ToString(CultureInfo.InvariantCulture));
lock (InstanceLockObject) {
if (boolFlags[Flag_notifying]) {
// don't do double notifications.
Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::CompletionStatusChanged: aborting because we're already notifying.");
return;
}
boolFlags[Flag_notifying] = true;
}
int i = lastSeenCount;
try {
int oldest = OldestEntryNumber;
int count = EntryCount + oldest;
// Ensure lastSeenCount is within bounds. This deals with the case where the event log has been cleared between
// notifications.
if (lastSeenCount < oldest || lastSeenCount > count) {
lastSeenCount = oldest;
i = lastSeenCount;
}
// NOTE, Microsoft: We have a double loop here so that we access the
// EntryCount property as infrequently as possible. (It may be expensive
// to get the property.) Even though there are two loops, they will together
// only execute as many times as (final value of EntryCount) - lastSeenCount.
Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::CompletionStatusChanged: OldestEntryNumber is " + OldestEntryNumber + ", EntryCount is " + EntryCount);
while (i < count) {
while (i < count) {
EventLogEntry entry = GetEntryWithOldest(i);
if (this.SynchronizingObject != null && this.SynchronizingObject.InvokeRequired)
this.SynchronizingObject.BeginInvoke(this.onEntryWrittenHandler, new object[]{this, new EntryWrittenEventArgs(entry)});
else
onEntryWrittenHandler(this, new EntryWrittenEventArgs(entry));
i++;
}
oldest = OldestEntryNumber;
count = EntryCount + oldest;
}
}
catch (Exception e) {
Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::CompletionStatusChanged: Caught exception notifying event handlers: " + e.ToString());
}
try {
// if the user cleared the log while we were receiving events, the call to GetEntryWithOldest above could have
// thrown an exception and i could be too large. Make sure we don't set lastSeenCount to something bogus.
int newCount = EntryCount + OldestEntryNumber;
if (i > newCount)
lastSeenCount = newCount;
else
lastSeenCount = i;
Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::CompletionStatusChanged: finishing at " + lastSeenCount.ToString(CultureInfo.InvariantCulture));
}
catch (Win32Exception e) {
Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::CompletionStatusChanged: Caught exception updating last entry number: " + e.ToString());
}
lock (InstanceLockObject) {
boolFlags[Flag_notifying] = false;
}
}
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
/// <devdoc>
/// </devdoc>
internal void Dispose(bool disposing) {
try {
if (disposing) {
//Dispose unmanaged and managed resources
if (IsOpen) {
Close();
}
// This is probably unnecessary
if (readHandle != null) {
readHandle.Close();
readHandle = null;
}
if (writeHandle != null) {
writeHandle.Close();
writeHandle = null;
}
}
}
finally {
messageLibraries = null;
this.boolFlags[Flag_disposed] = true;
}
}
/// <devdoc>
/// </devdoc>
public void EndInit() {
string currentMachineName = this.machineName;
EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Write, currentMachineName);
permission.Demand();
boolFlags[Flag_initializing] = false;
if (boolFlags[Flag_monitoring])
StartListening(currentMachineName, GetLogName(currentMachineName));
}
[ResourceExposure(ResourceScope.None)]
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
internal string FormatMessageWrapper(string dllNameList, uint messageNum, string[] insertionStrings) {
if (dllNameList == null)
return null;
if (insertionStrings == null)
insertionStrings = new string[0];
string[] listDll = dllNameList.Split(';');
// Find first mesage in DLL list
foreach ( string dllName in listDll) {
if (dllName == null || dllName.Length == 0)
continue;
SafeLibraryHandle hModule = null;
// if the EventLog is open, then we want to cache the library in our hashtable. Otherwise
// we'll just load it and free it after we're done.
if (IsOpen) {
hModule = MessageLibraries[dllName] as SafeLibraryHandle;
if (hModule == null || hModule.IsInvalid) {
hModule = SafeLibraryHandle.LoadLibraryEx(dllName, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE);
MessageLibraries[dllName] = hModule;
}
}
else {
hModule = SafeLibraryHandle.LoadLibraryEx(dllName, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE);
}
if (hModule.IsInvalid)
continue;
string msg = null;
try {
msg = EventLog.TryFormatMessage(hModule, messageNum, insertionStrings);
}
finally {
if (!IsOpen) {
hModule.Close();
}
}
if ( msg != null ) {
return msg;
}
}
return null;
}
/// <devdoc>
/// Gets an array of EventLogEntry's, one for each entry in the log.
/// </devdoc>
[ResourceExposure(ResourceScope.None)]
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
internal EventLogEntry[] GetAllEntries() {
// we could just call getEntryAt() on all the entries, but it'll be faster
// if we grab multiple entries at once.
string currentMachineName = this.machineName;
if (!IsOpenForRead)
OpenForRead(currentMachineName);
EventLogEntry[] entries = new EventLogEntry[EntryCount];
int idx = 0;
int oldestEntry = OldestEntryNumber;
int bytesRead;
int minBytesNeeded;
int error = 0;
while (idx < entries.Length) {
byte[] buf = new byte[BUF_SIZE];
bool success = UnsafeNativeMethods.ReadEventLog(readHandle, NativeMethods.FORWARDS_READ | NativeMethods.SEEK_READ,
oldestEntry+idx, buf, buf.Length, out bytesRead, out minBytesNeeded);
if (!success) {
error = Marshal.GetLastWin32Error();
// NOTE, Microsoft: ERROR_PROC_NOT_FOUND used to get returned, but I think that
// was because I was calling GetLastError directly instead of GetLastWin32Error.
// Making the buffer bigger and trying again seemed to work. I've removed the check
// for ERROR_PROC_NOT_FOUND because I don't think it's necessary any more, but
// I can't prove it...
Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "Error from ReadEventLog is " + error.ToString(CultureInfo.InvariantCulture));
#if !RETRY_ON_ALL_ERRORS
if (error == NativeMethods.ERROR_INSUFFICIENT_BUFFER || error == NativeMethods.ERROR_EVENTLOG_FILE_CHANGED) {
#endif
if (error == NativeMethods.ERROR_EVENTLOG_FILE_CHANGED) {
// somewhere along the way the event log file changed - probably it
// got cleared while we were looping here. Reset the handle and
// try again.
Reset(currentMachineName);
}
// try again with a bigger buffer if necessary
else if (minBytesNeeded > buf.Length) {
Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "Increasing buffer size from " + buf.Length.ToString(CultureInfo.InvariantCulture) + " to " + minBytesNeeded.ToString(CultureInfo.InvariantCulture) + " bytes");
buf = new byte[minBytesNeeded];
}
success = UnsafeNativeMethods.ReadEventLog(readHandle, NativeMethods.FORWARDS_READ | NativeMethods.SEEK_READ,
oldestEntry+idx, buf, buf.Length, out bytesRead, out minBytesNeeded);
if (!success)
// we'll just stop right here.
break;
#if !RETRY_ON_ALL_ERRORS
}
else {
break;
}
#endif
error = 0;
}
entries[idx] = new EventLogEntry(buf, 0, this);
int sum = IntFrom(buf, 0);
idx++;
while (sum < bytesRead && idx < entries.Length) {
entries[idx] = new EventLogEntry(buf, sum, this);
sum += IntFrom(buf, sum);
idx++;
}
}
if (idx != entries.Length) {
if (error != 0)
throw new InvalidOperationException(SR.GetString(SR.CantRetrieveEntries), SharedUtils.CreateSafeWin32Exception(error));
else
throw new InvalidOperationException(SR.GetString(SR.CantRetrieveEntries));
}
return entries;
}
/// <devdoc>
/// Searches the cache for an entry with the given index
/// </devdoc>
private int GetCachedEntryPos(int entryIndex) {
if (cache == null || (boolFlags[Flag_forwards] && entryIndex < firstCachedEntry) ||
(!boolFlags[Flag_forwards] && entryIndex > firstCachedEntry) || firstCachedEntry == -1) {
// the index falls before anything we have in the cache, or the cache
// is not yet valid
return -1;
}
// we only know where the beginning of the cache is, not the end, so even
// if it's past the end of the cache, we'll have to search through the whole
// cache to find out.
// we're betting heavily that the one they want to see now is close
// to the one they asked for last time. We start looking where we
// stopped last time.
// We have two loops, one to go forwards and one to go backwards. Only one
// of them will ever be executed.
while (lastSeenEntry < entryIndex) {
lastSeenEntry++;
if (boolFlags[Flag_forwards]) {
lastSeenPos = GetNextEntryPos(lastSeenPos);
if (lastSeenPos >= bytesCached)
break;
}
else {
lastSeenPos = GetPreviousEntryPos(lastSeenPos);
if (lastSeenPos < 0)
break;
}
}
while (lastSeenEntry > entryIndex) {
lastSeenEntry--;
if (boolFlags[Flag_forwards]) {
lastSeenPos = GetPreviousEntryPos(lastSeenPos);
if (lastSeenPos < 0)
break;
}
else {
lastSeenPos = GetNextEntryPos(lastSeenPos);
if (lastSeenPos >= bytesCached)
break;
}
}
if (lastSeenPos >= bytesCached) {
// we ran past the end. move back to the last one and return -1
lastSeenPos = GetPreviousEntryPos(lastSeenPos);
if (boolFlags[Flag_forwards])
lastSeenEntry--;
else
lastSeenEntry++;
return -1;
}
else if (lastSeenPos < 0) {
// we ran past the beginning. move back to the first one and return -1
lastSeenPos = 0;
if (boolFlags[Flag_forwards])
lastSeenEntry++;
else
lastSeenEntry--;
return -1;
}
else {
// we found it.
return lastSeenPos;
}
}
/// <devdoc>
/// Gets the entry at the given index
/// </devdoc>
internal EventLogEntry GetEntryAt(int index) {
EventLogEntry entry = GetEntryAtNoThrow(index);
if (entry == null)
throw new ArgumentException(SR.GetString(SR.IndexOutOfBounds, index.ToString(CultureInfo.CurrentCulture)));
return entry;
}
internal EventLogEntry GetEntryAtNoThrow(int index) {
if (!IsOpenForRead)
OpenForRead(this.machineName);
if (index < 0 || index >= EntryCount)
return null;
//
index += OldestEntryNumber;
EventLogEntry entry = null;
try {
entry = GetEntryWithOldest(index);
}
catch (InvalidOperationException) {
// This would be common in rapidly spinning EventLog (i.e. logs which are rapidly receiving
// new events while discarding old ones in a rolling fashion) or if the EventLog is cleared asynchronously.
//
// EventLogEntryCollection heuristics is little bit convoluted due to the inherent ----s.
// The enumerator predominantly operates on the index from the last “known” oldest entry
// (refreshing on every iteration is probalby not right here) and it has no notion of the
// collection size when it is created or while it is operating. It would keep on enumerating
// until the index become invalid.
//
// Throwing InvalidOperationException to let you know that your enumerator has been invalidated
// because of changes underneath is probably not the most useful behavior.
}
return entry;
}
[ResourceExposure(ResourceScope.None)]
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
private EventLogEntry GetEntryWithOldest(int index) {
EventLogEntry entry = null;
int entryPos = GetCachedEntryPos(index);
if (entryPos >= 0) {
entry = new EventLogEntry(cache, entryPos, this);
return entry;
}
string currentMachineName = this.machineName;
// if we haven't seen the one after this, we were probably going
// forwards.
int flags = 0;
if (GetCachedEntryPos(index+1) < 0) {
flags = NativeMethods.FORWARDS_READ | NativeMethods.SEEK_READ;
boolFlags[Flag_forwards] = true;
}
else {
flags = NativeMethods.BACKWARDS_READ | NativeMethods.SEEK_READ;
boolFlags[Flag_forwards] = false;
}
cache = new byte[BUF_SIZE];
int bytesRead;
int minBytesNeeded;
bool success = UnsafeNativeMethods.ReadEventLog(readHandle, flags, index,
cache, cache.Length, out bytesRead, out minBytesNeeded);
if (!success) {
int error = Marshal.GetLastWin32Error();
// NOTE, Microsoft: ERROR_PROC_NOT_FOUND used to get returned, but I think that
// was because I was calling GetLastError directly instead of GetLastWin32Error.
// Making the buffer bigger and trying again seemed to work. I've removed the check
// for ERROR_PROC_NOT_FOUND because I don't think it's necessary any more, but
// I can't prove it...
Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "Error from ReadEventLog is " + error.ToString(CultureInfo.InvariantCulture));
if (error == NativeMethods.ERROR_INSUFFICIENT_BUFFER || error == NativeMethods.ERROR_EVENTLOG_FILE_CHANGED) {
if (error == NativeMethods.ERROR_EVENTLOG_FILE_CHANGED) {
// Reset() sets the cache null. But since we're going to call ReadEventLog right after this,
// we need the cache to be something valid. We'll reuse the old byte array rather
// than creating a new one.
byte[] tempcache = cache;
Reset(currentMachineName);
cache = tempcache;
} else {
// try again with a bigger buffer.
if (minBytesNeeded > cache.Length) {
cache = new byte[minBytesNeeded];
}
}
success = UnsafeNativeMethods.ReadEventLog(readHandle, NativeMethods.FORWARDS_READ | NativeMethods.SEEK_READ, index,
cache, cache.Length, out bytesRead, out minBytesNeeded);
}
if (!success) {
throw new InvalidOperationException(SR.GetString(SR.CantReadLogEntryAt, index.ToString(CultureInfo.CurrentCulture)), SharedUtils.CreateSafeWin32Exception());
}
}
bytesCached = bytesRead;
firstCachedEntry = index;
lastSeenEntry = index;
lastSeenPos = 0;
return new EventLogEntry(cache, 0, this);
}
[ResourceExposure(ResourceScope.Machine)]
[ResourceConsumption(ResourceScope.Machine)]
internal static RegistryKey GetEventLogRegKey(string machine, bool writable) {
RegistryKey lmkey = null;
try {
if (machine.Equals(".")) {
lmkey = Registry.LocalMachine;
}
else {
lmkey = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, machine);
}
if (lmkey != null)
return lmkey.OpenSubKey(EventLogKey, writable);
}
finally {
if (lmkey != null) lmkey.Close();
}
return null;
}
[ResourceExposure(ResourceScope.Machine)]
[ResourceConsumption(ResourceScope.Machine)]
private RegistryKey GetLogRegKey(string currentMachineName, bool writable) {
string logname = GetLogName(currentMachineName);
// we need to verify the logname here again because we might have tried to look it up
// based on the source and failed.
if (!ValidLogName(logname, false))
throw new InvalidOperationException(SR.GetString(SR.BadLogName));
RegistryKey eventkey = null;
RegistryKey logkey = null;
try {
eventkey = GetEventLogRegKey(currentMachineName, false);
if (eventkey == null)
throw new InvalidOperationException(SR.GetString(SR.RegKeyMissingShort, EventLogKey, currentMachineName));
logkey = eventkey.OpenSubKey(logname, writable);
if (logkey == null)
throw new InvalidOperationException(SR.GetString(SR.MissingLog, logname, currentMachineName));
}
finally {
if (eventkey != null) eventkey.Close();
}
return logkey;
}
[ResourceExposure(ResourceScope.None)]
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
private object GetLogRegValue(string currentMachineName, string valuename) {
PermissionSet permissionSet = EventLog._UnsafeGetAssertPermSet();
permissionSet.Assert();
RegistryKey logkey = null;
try {
logkey = GetLogRegKey(currentMachineName, false);
if (logkey == null)
throw new InvalidOperationException(SR.GetString(SR.MissingLog, GetLogName(currentMachineName), currentMachineName));
object val = logkey.GetValue(valuename);
return val;
}
finally {
if (logkey != null) logkey.Close();
// Revert registry and environment permission asserts
CodeAccessPermission.RevertAssert();
}
}
/// <devdoc>
/// Finds the index into the cache where the next entry starts
/// </devdoc>
private int GetNextEntryPos(int pos) {
return pos + IntFrom(cache, pos);
}
/// <devdoc>
/// Finds the index into the cache where the previous entry starts
/// </devdoc>
private int GetPreviousEntryPos(int pos) {
// the entries in our buffer come back like this:
// <length 1> ... <data> ... <length 1> <length 2> ... <data> ... <length 2> ...
// In other words, the length for each entry is repeated at the beginning and
// at the end. This makes it easy to navigate forwards and backwards through
// the buffer.
return pos - IntFrom(cache, pos - 4);
}
[ResourceExposure(ResourceScope.Machine)]
[ResourceConsumption(ResourceScope.Machine)]
internal static string GetDllPath(string machineName) {
return Path.Combine(SharedUtils.GetLatestBuildDllDirectory(machineName), DllName);
}
/// <devdoc>
/// Extracts a 32-bit integer from the ubyte buffer, beginning at the byte offset
/// specified in offset.
/// </devdoc>
private static int IntFrom(byte[] buf, int offset) {
// assumes Little Endian byte order.
return(unchecked((int)0xFF000000) & (buf[offset+3] << 24)) | (0xFF0000 & (buf[offset+2] << 16)) |
(0xFF00 & (buf[offset+1] << 8)) | (0xFF & (buf[offset]));
}
[ComVisible(false)]
[ResourceExposure(ResourceScope.None)]
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
public void ModifyOverflowPolicy(OverflowAction action, int retentionDays) {
string currentMachineName = this.machineName;
EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Administer, currentMachineName);
permission.Demand();
if (action < OverflowAction.DoNotOverwrite || action > OverflowAction.OverwriteOlder)
throw new InvalidEnumArgumentException("action", (int)action, typeof(OverflowAction));
// this is a long because in the if statement we may need to store values as
// large as UInt32.MaxValue - 1. This would overflow an int.
long retentionvalue = (long) action;
if (action == OverflowAction.OverwriteOlder) {
if (retentionDays < 1 || retentionDays > 365)
throw new ArgumentOutOfRangeException(SR.GetString(SR.RentionDaysOutOfRange));
retentionvalue = (long) retentionDays * SecondsPerDay;
}
PermissionSet permissionSet = EventLog._UnsafeGetAssertPermSet();
permissionSet.Assert();
using (RegistryKey logkey = GetLogRegKey(currentMachineName, true))
logkey.SetValue("Retention", retentionvalue, RegistryValueKind.DWord);
}
/// <devdoc>
/// Opens the event log with read access
/// </devdoc>
[ResourceExposure(ResourceScope.None)]
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
[SuppressMessage("Microsoft.Security", "CA2103:ReviewImperativeSecurity", Justification = "Microsoft: Safe, machineName doesn't change")]
private void OpenForRead(string currentMachineName) {
Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::OpenForRead");
EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Administer, currentMachineName);
permission.Demand();
//Cannot allocate the readHandle if the object has been disposed, since finalization has been suppressed.
if (this.boolFlags[Flag_disposed])
throw new ObjectDisposedException(GetType().Name);
string logname = GetLogName(currentMachineName);
if (logname == null || logname.Length==0)
throw new ArgumentException(SR.GetString(SR.MissingLogProperty));
if (!EventLog.Exists(logname, currentMachineName) ) // do not open non-existing Log [Microsoft]
throw new InvalidOperationException( SR.GetString(SR.LogDoesNotExists, logname, currentMachineName) );
//Check environment before calling api
SharedUtils.CheckEnvironment();
// Clean up cache variables.
// [Microsoft] The initilizing code is put here to guarantee, that first read of events
// from log file will start by filling up the cache buffer.
lastSeenEntry = 0;
lastSeenPos = 0;
bytesCached = 0;
firstCachedEntry = -1;
SafeEventLogReadHandle handle = SafeEventLogReadHandle.OpenEventLog(currentMachineName, logname);
if (handle.IsInvalid) {
Win32Exception e = null;
if (Marshal.GetLastWin32Error() != 0) {
e = SharedUtils.CreateSafeWin32Exception();
}
throw new InvalidOperationException(SR.GetString(SR.CantOpenLog, logname.ToString(), currentMachineName), e);
}
readHandle = handle;
}
/// <devdoc>
/// Opens the event log with write access
/// </devdoc>
[ResourceExposure(ResourceScope.None)]
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
private void OpenForWrite(string currentMachineName) {
//Cannot allocate the writeHandle if the object has been disposed, since finalization has been suppressed.
if (this.boolFlags[Flag_disposed])
throw new ObjectDisposedException(GetType().Name);
Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::OpenForWrite");
if (sourceName == null || sourceName.Length==0)
throw new ArgumentException(SR.GetString(SR.NeedSourceToOpen));
//Check environment before calling api
SharedUtils.CheckEnvironment();
SafeEventLogWriteHandle handle = SafeEventLogWriteHandle.RegisterEventSource(currentMachineName, sourceName);
if (handle.IsInvalid) {
Win32Exception e = null;
if (Marshal.GetLastWin32Error() != 0) {
e = SharedUtils.CreateSafeWin32Exception();
}
throw new InvalidOperationException(SR.GetString(SR.CantOpenLogAccess, sourceName), e);
}
writeHandle = handle;
}
[ComVisible(false)]
[ResourceExposure(ResourceScope.Machine)]
[ResourceConsumption(ResourceScope.Machine)]
public void RegisterDisplayName(string resourceFile, long resourceId) {
string currentMachineName = this.machineName;
EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Administer, currentMachineName);
permission.Demand();
PermissionSet permissionSet = EventLog._UnsafeGetAssertPermSet();
permissionSet.Assert();
using (RegistryKey logkey = GetLogRegKey(currentMachineName, true)) {
logkey.SetValue("DisplayNameFile", resourceFile, RegistryValueKind.ExpandString);
logkey.SetValue("DisplayNameID", resourceId, RegistryValueKind.DWord);
}
}
private void Reset(string currentMachineName) {
Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::Reset");
// save the state we're in now
bool openRead = IsOpenForRead;
bool openWrite = IsOpenForWrite;
bool isMonitoring = boolFlags[Flag_monitoring];
bool isListening = boolFlags[Flag_registeredAsListener];
// close everything down
Close(currentMachineName);
cache = null;
// and get us back into the same state as before
if (openRead)
OpenForRead(currentMachineName);
if (openWrite)
OpenForWrite(currentMachineName);
if (isListening)
StartListening(currentMachineName, GetLogName(currentMachineName));
boolFlags[Flag_monitoring] = isMonitoring;
}
[HostProtection(Synchronization=true)]
private static void RemoveListenerComponent(EventLogInternal component, string compLogName) {
lock (InternalSyncObject) {
Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::RemoveListenerComponent(" + compLogName + ")");
LogListeningInfo info = (LogListeningInfo) listenerInfos[compLogName];
Debug.Assert(info != null);
// remove the requested component from the list.
info.listeningComponents.Remove(component);
if (info.listeningComponents.Count != 0)
return;
// if that was the last interested compononent, destroy the handles and stop listening.
info.handleOwner.Dispose();
//Unregister the thread pool wait handle
info.registeredWaitHandle.Unregister(info.waitHandle);
// close the handle
info.waitHandle.Close();
listenerInfos[compLogName] = null;
}
}
/// <devdoc>
/// Sets up the event monitoring mechanism. We don't track event log changes
/// unless someone is interested, so we set this up on demand.
/// </devdoc>
[HostProtection(Synchronization=true, ExternalThreading=true)]
private void StartListening(string currentMachineName, string currentLogName) {
// make sure we don't fire events for entries that are already there
Debug.Assert(!boolFlags[Flag_registeredAsListener], "StartListening called with boolFlags[Flag_registeredAsListener] true.");
lastSeenCount = EntryCount + OldestEntryNumber;
Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::StartListening: lastSeenCount = " + lastSeenCount);
AddListenerComponent(this, currentMachineName, currentLogName);
boolFlags[Flag_registeredAsListener] = true;
}
private void StartRaisingEvents(string currentMachineName, string currentLogName) {
if (!boolFlags[Flag_initializing] && !boolFlags[Flag_monitoring] && !parent.ComponentDesignMode) {
StartListening(currentMachineName, currentLogName);
}
boolFlags[Flag_monitoring] = true;
}
private static void StaticCompletionCallback(object context, bool wasSignaled) {
LogListeningInfo info = (LogListeningInfo) context;
if (info == null)
return;
// get a snapshot of the components to fire the event on
EventLogInternal[] interestedComponents;
lock (InternalSyncObject) {
interestedComponents = (EventLogInternal[])info.listeningComponents.ToArray(typeof(EventLogInternal));
}
Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::StaticCompletionCallback: notifying " + interestedComponents.Length + " components.");
for (int i = 0; i < interestedComponents.Length; i++) {
try {
if (interestedComponents[i] != null) {
interestedComponents[i].CompletionCallback(null);
}
} catch (ObjectDisposedException) {
// The EventLog that was registered to listen has been disposed. Nothing much we can do here
// we don't want to propigate this error up as it will likely be unhandled and will cause the app
// to crash.
Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::StaticCompletionCallback: ignored an ObjectDisposedException");
}
}
}
/// <devdoc>
/// Tears down the event listening mechanism. This is called when the last
/// interested party removes their event handler.
/// </devdoc>
[HostProtection(Synchronization=true, ExternalThreading=true)]
private void StopListening(/*string currentMachineName,*/ string currentLogName) {
Debug.Assert(boolFlags[Flag_registeredAsListener], "StopListening called without StartListening.");
RemoveListenerComponent(this, currentLogName);
boolFlags[Flag_registeredAsListener] = false;
}
/// <devdoc>
/// </devdoc>
private void StopRaisingEvents(/*string currentMachineName,*/ string currentLogName) {
if (!boolFlags[Flag_initializing] && boolFlags[Flag_monitoring] && !parent.ComponentDesignMode) {
StopListening(currentLogName);
}
boolFlags[Flag_monitoring] = false;
}
// CharIsPrintable used to be Char.IsPrintable, but Jay removed it and
// is forcing people to use the Unicode categories themselves. Copied
// the code here.
private static bool CharIsPrintable(char c) {
UnicodeCategory uc = Char.GetUnicodeCategory(c);
return (!(uc == UnicodeCategory.Control) || (uc == UnicodeCategory.Format) ||
(uc == UnicodeCategory.LineSeparator) || (uc == UnicodeCategory.ParagraphSeparator) ||
(uc == UnicodeCategory.OtherNotAssigned));
}
// SECREVIEW: Make sure this method catches all the strange cases.
internal static bool ValidLogName(string logName, bool ignoreEmpty) {
// No need to trim here since the next check will verify that there are no spaces.
// We need to ignore the empty string as an invalid log name sometimes because it can
// be passed in from our default constructor.
if (logName.Length == 0 && !ignoreEmpty)
return false;
//any space, backslash, asterisk, or question mark is bad
//any non-printable characters are also bad
foreach (char c in logName)
if (!CharIsPrintable(c) || (c == '\\') || (c == '*') || (c == '?'))
return false;
return true;
}
[ResourceExposure(ResourceScope.None)]
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
[SuppressMessage("Microsoft.Security", "CA2103:ReviewImperativeSecurity", Justification = "Microsoft: Safe, machineName doesn't change")]
private void VerifyAndCreateSource(string sourceName, string currentMachineName) {
if (boolFlags[Flag_sourceVerified])
return;
if (!EventLog.SourceExists(sourceName, currentMachineName, true)) {
Mutex mutex = null;
RuntimeHelpers.PrepareConstrainedRegions();
try {
SharedUtils.EnterMutex(eventLogMutexName, ref mutex);
if (!EventLog.SourceExists(sourceName, currentMachineName, true)) {
if (GetLogName(currentMachineName) == null)
this.logName = "Application";
// we automatically add an entry in the registry if there's not already
// one there for this source
EventLog.CreateEventSource(new EventSourceCreationData(sourceName, GetLogName(currentMachineName), currentMachineName));
// The user may have set a custom log and tried to read it before trying to
// write. Due to a quirk in the event log API, we would have opened the Application
// log to read (because the custom log wasn't there). Now that we've created
// the custom log, we should close so that when we re-open, we get a read
// handle on the _new_ log instead of the Application log.
Reset(currentMachineName);
}
else {
string rightLogName = EventLog.LogNameFromSourceName(sourceName, currentMachineName);
string currentLogName = GetLogName(currentMachineName);
if (rightLogName != null && currentLogName != null && String.Compare(rightLogName, currentLogName, StringComparison.OrdinalIgnoreCase) != 0)
throw new ArgumentException(SR.GetString(SR.LogSourceMismatch, Source.ToString(), currentLogName, rightLogName));
}
}
finally {
if (mutex != null) {
mutex.ReleaseMutex();
mutex.Close();
}
}
}
else {
EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Write, currentMachineName);
permission.Demand();
string rightLogName = EventLog._InternalLogNameFromSourceName(sourceName, currentMachineName);
string currentLogName = GetLogName(currentMachineName);
if (rightLogName != null && currentLogName != null && String.Compare(rightLogName, currentLogName, StringComparison.OrdinalIgnoreCase) != 0)
throw new ArgumentException(SR.GetString(SR.LogSourceMismatch, Source.ToString(), currentLogName, rightLogName));
}
boolFlags[Flag_sourceVerified] = true;
}
/// <devdoc>
/// <para>
/// Writes an information type entry with the given message text to the event log.
/// </para>
/// </devdoc>
public void WriteEntry(string message) {
WriteEntry(message, EventLogEntryType.Information, (short) 0, 0, null);
}
/// <devdoc>
/// <para>
/// Writes an entry of the specified <see cref='System.Diagnostics.EventLogEntryType'/> to the event log. Valid types are
/// <see langword='Error'/>, <see langword='Warning'/>, <see langword='Information'/>,
/// <see langword='Success Audit'/>, and <see langword='Failure Audit'/>.
/// </para>
/// </devdoc>
public void WriteEntry(string message, EventLogEntryType type) {
WriteEntry(message, type, (short) 0, 0, null);
}
/// <devdoc>
/// <para>
/// Writes an entry of the specified <see cref='System.Diagnostics.EventLogEntryType'/>
/// and with the
/// user-defined <paramref name="eventID"/>
/// to
/// the event log.
/// </para>
/// </devdoc>
public void WriteEntry(string message, EventLogEntryType type, int eventID) {
WriteEntry(message, type, eventID, 0, null);
}
/// <devdoc>
/// <para>
/// Writes an entry of the specified type with the
/// user-defined <paramref name="eventID"/> and <paramref name="category"/>
/// to the event log. The <paramref name="category"/>
/// can be used by the event viewer to filter events in the log.
/// </para>
/// </devdoc>
public void WriteEntry(string message, EventLogEntryType type, int eventID, short category) {
WriteEntry(message, type, eventID, category, null);
}
/// <devdoc>
/// <para>
/// Writes an entry of the specified type with the
/// user-defined <paramref name="eventID"/> and <paramref name="category"/> to the event log, and appends binary data to
/// the message. The Event Viewer does not interpret this data; it
/// displays raw data only in a combined hexadecimal and text format.
/// </para>
/// </devdoc>
public void WriteEntry(string message, EventLogEntryType type, int eventID, short category,
byte[] rawData) {
if (eventID < 0 || eventID > ushort.MaxValue)
throw new ArgumentException(SR.GetString(SR.EventID, eventID, 0, (int)ushort.MaxValue));
if (Source.Length == 0)
throw new ArgumentException(SR.GetString(SR.NeedSourceToWrite));
if (!Enum.IsDefined(typeof(EventLogEntryType), type))
throw new InvalidEnumArgumentException("type", (int)type, typeof(EventLogEntryType));
string currentMachineName = machineName;
if (!boolFlags[Flag_writeGranted]) {
EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Write, currentMachineName);
permission.Demand();
boolFlags[Flag_writeGranted] = true;
}
VerifyAndCreateSource(sourceName, currentMachineName);
// now that the source has been hooked up to our DLL, we can use "normal"
// (message-file driven) logging techniques.
// Our DLL has 64K different entries; all of them just display the first
// insertion string.
InternalWriteEvent((uint)eventID, (ushort)category, type, new string[] { message}, rawData, currentMachineName);
}
[ComVisible(false)]
public void WriteEvent(EventInstance instance, params Object[] values) {
WriteEvent(instance, null, values);
}
[ComVisible(false)]
public void WriteEvent(EventInstance instance, byte[] data, params Object[] values) {
if (instance == null)
throw new ArgumentNullException("instance");
if (Source.Length == 0)
throw new ArgumentException(SR.GetString(SR.NeedSourceToWrite));
string currentMachineName = machineName;
if (!boolFlags[Flag_writeGranted]) {
EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Write, currentMachineName);
permission.Demand();
boolFlags[Flag_writeGranted] = true;
}
VerifyAndCreateSource(Source, currentMachineName);
string[] strings = null;
if (values != null) {
strings = new string[values.Length];
for (int i=0; i<values.Length; i++) {
if (values[i] != null)
strings[i] = values[i].ToString();
else
strings[i] = String.Empty;
}
}
InternalWriteEvent((uint) instance.InstanceId, (ushort) instance.CategoryId, instance.EntryType, strings, data, currentMachineName);
}
[ResourceExposure(ResourceScope.None)]
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
private void InternalWriteEvent(uint eventID, ushort category, EventLogEntryType type, string[] strings,
byte[] rawData, string currentMachineName) {
// check arguments
if (strings == null)
strings = new string[0];
if (strings.Length >= 256)
throw new ArgumentException(SR.GetString(SR.TooManyReplacementStrings));
for (int i = 0; i < strings.Length; i++) {
if (strings[i] == null)
strings[i] = String.Empty;
// make sure the strings aren't too long. MSDN says each string has a limit of 32k (32768) characters, but
// experimentation shows that it doesn't like anything larger than 32766
if (strings[i].Length > 32766)
throw new ArgumentException(SR.GetString(SR.LogEntryTooLong));
}
if (rawData == null)
rawData = new byte[0];
if (Source.Length == 0)
throw new ArgumentException(SR.GetString(SR.NeedSourceToWrite));
if (!IsOpenForWrite)
OpenForWrite(currentMachineName);
// pin each of the strings in memory
IntPtr[] stringRoots = new IntPtr[strings.Length];
GCHandle[] stringHandles = new GCHandle[strings.Length];
GCHandle stringsRootHandle = GCHandle.Alloc(stringRoots, GCHandleType.Pinned);
try {
for (int strIndex = 0; strIndex < strings.Length; strIndex++) {
stringHandles[strIndex] = GCHandle.Alloc(strings[strIndex], GCHandleType.Pinned);
stringRoots[strIndex] = stringHandles[strIndex].AddrOfPinnedObject();
}
byte[] sid = null;
// actually report the event
bool success = UnsafeNativeMethods.ReportEvent(writeHandle, (short) type, category, eventID,
sid, (short) strings.Length, rawData.Length, new HandleRef(this, stringsRootHandle.AddrOfPinnedObject()), rawData);
if (!success) {
//Trace("WriteEvent", "Throwing Win32Exception");
throw SharedUtils.CreateSafeWin32Exception();
}
}
finally {
// now free the pinned strings
for (int i = 0; i < strings.Length; i++) {
if (stringHandles[i].IsAllocated)
stringHandles[i].Free();
}
stringsRootHandle.Free();
}
}
private class LogListeningInfo {
public EventLogInternal handleOwner;
public RegisteredWaitHandle registeredWaitHandle;
public WaitHandle waitHandle;
public ArrayList listeningComponents = new ArrayList();
}
}
}