Merge pull request #498 from Unroll-Me/master
[mono.git] / mcs / class / System / System.Diagnostics / Win32EventLog.cs
index cb2d9f727b3d6d7b868121b09d2c74543b0a3825..40fe3a36b157ac461654e70c532d17ec06966ce7 100644 (file)
@@ -35,6 +35,7 @@ using System.Globalization;
 using System.IO;
 using System.Runtime.InteropServices;
 using System.Text;
+using System.Threading;
 
 using Microsoft.Win32;
 
@@ -43,6 +44,11 @@ namespace System.Diagnostics
        internal class Win32EventLog : EventLogImpl
        {
                private const int MESSAGE_NOT_FOUND = 317;
+               private ManualResetEvent _notifyResetEvent;
+               private IntPtr _readHandle;
+               private Thread _notifyThread;
+               private int _lastEntryWritten;
+               private bool _notifying;
 
                public Win32EventLog (EventLog coreEventLog)
                        : base (coreEventLog)
@@ -55,20 +61,17 @@ namespace System.Diagnostics
 
                public override void Clear ()
                {
-                       IntPtr hEventLog = OpenEventLog ();
-                       try {
-                               int ret = PInvoke.ClearEventLog (hEventLog, null);
-                               if (ret != 1) {
-                                       throw new Win32Exception (Marshal.GetLastWin32Error ());
-                               }
-                       } finally {
-                               CloseEventLog (hEventLog);
-                       }
+                       int ret = PInvoke.ClearEventLog (ReadHandle, null);
+                       if (ret != 1)
+                               throw new Win32Exception (Marshal.GetLastWin32Error ());
                }
 
                public override void Close ()
                {
-                       // we don't hold any unmanaged resources
+                       if (_readHandle != IntPtr.Zero) {
+                               CloseEventLog (_readHandle);
+                               _readHandle = IntPtr.Zero;
+                       }
                }
 
                public override void CreateEventSource (EventSourceCreationData sourceData)
@@ -236,17 +239,11 @@ namespace System.Diagnostics
 
                protected override int GetEntryCount ()
                {
-                       IntPtr hEventLog = OpenEventLog ();
-                       try {
-                               int entryCount = 0;
-                               int retVal = PInvoke.GetNumberOfEventLogRecords (hEventLog, ref entryCount);
-                               if (retVal != 1) {
-                                       throw new Win32Exception (Marshal.GetLastWin32Error ());
-                               }
-                               return entryCount;
-                       } finally {
-                               CloseEventLog (hEventLog);
-                       }
+                       int entryCount = 0;
+                       int retVal = PInvoke.GetNumberOfEventLogRecords (ReadHandle, ref entryCount);
+                       if (retVal != 1)
+                               throw new Win32Exception (Marshal.GetLastWin32Error ());
+                       return entryCount;
                }
 
                protected override EventLogEntry GetEntry (int index)
@@ -257,103 +254,91 @@ namespace System.Diagnostics
 
                        index += OldestEventLogEntry;
 
-                       IntPtr hEventLog = OpenEventLog ();
-                       try {
-                               int bytesRead = 0;
-                               int minBufferNeeded = 0;
-
-                               byte [] buffer = new byte [0x7ffff]; // according to MSDN this is the max size of the buffer
-                               int length = buffer.Length;
-
-                               int ret = PInvoke.ReadEventLog (hEventLog, ReadFlags.Seek |
-                                       ReadFlags.ForwardsRead, index, buffer, length,
-                                       ref bytesRead, ref minBufferNeeded);
-                               if (ret != 1) {
-                                       throw new Win32Exception (Marshal.GetLastWin32Error ());
-                               }
-
-                               MemoryStream ms = new MemoryStream (buffer);
-                               BinaryReader br = new BinaryReader (ms);
-
-                               // skip first 8 bytes
-                               br.ReadBytes (8);
-
-                               int recordNumber = br.ReadInt32 (); // 8
-
-                               int timeGeneratedSeconds = br.ReadInt32 (); // 12
-                               int timeWrittenSeconds = br.ReadInt32 (); // 16
-                               uint instanceID = br.ReadUInt32 ();
-                               int eventID = EventLog.GetEventID (instanceID);
-                               short eventType = br.ReadInt16 (); // 24
-                               short numStrings = br.ReadInt16 (); ; // 26
-                               short categoryNumber = br.ReadInt16 (); ; // 28
-                               // skip reservedFlags
-                               br.ReadInt16 (); // 30
-                               // skip closingRecordNumber
-                               br.ReadInt32 (); // 32
-                               int stringOffset = br.ReadInt32 (); // 36
-                               int userSidLength = br.ReadInt32 (); // 40
-                               int userSidOffset = br.ReadInt32 (); // 44
-                               int dataLength = br.ReadInt32 (); // 48
-                               int dataOffset = br.ReadInt32 (); // 52
-
-                               DateTime timeGenerated = new DateTime (1970, 1, 1).AddSeconds (
-                                       timeGeneratedSeconds);
-
-                               DateTime timeWritten = new DateTime (1970, 1, 1).AddSeconds (
-                                       timeWrittenSeconds);
-
-                               StringBuilder sb = new StringBuilder ();
-                               while (br.PeekChar () != '\0')
-                                       sb.Append (br.ReadChar ());
-                               br.ReadChar (); // skip the null-char
-
-                               string sourceName = sb.ToString ();
-
-                               sb.Length = 0;
-                               while (br.PeekChar () != '\0')
-                                       sb.Append (br.ReadChar ());
-                               br.ReadChar (); // skip the null-char
-                               string machineName = sb.ToString ();
+                       int bytesRead = 0;
+                       int minBufferNeeded = 0;
+                       byte [] buffer = new byte [0x7ffff]; // according to MSDN this is the max size of the buffer
+
+                       ReadEventLog (index, buffer, ref bytesRead, ref minBufferNeeded);
+
+                       MemoryStream ms = new MemoryStream (buffer);
+                       BinaryReader br = new BinaryReader (ms);
+
+                       // skip first 8 bytes
+                       br.ReadBytes (8);
+
+                       int recordNumber = br.ReadInt32 (); // 8
+
+                       int timeGeneratedSeconds = br.ReadInt32 (); // 12
+                       int timeWrittenSeconds = br.ReadInt32 (); // 16
+                       uint instanceID = br.ReadUInt32 ();
+                       int eventID = EventLog.GetEventID (instanceID);
+                       short eventType = br.ReadInt16 (); // 24
+                       short numStrings = br.ReadInt16 (); ; // 26
+                       short categoryNumber = br.ReadInt16 (); ; // 28
+                       // skip reservedFlags
+                       br.ReadInt16 (); // 30
+                       // skip closingRecordNumber
+                       br.ReadInt32 (); // 32
+                       int stringOffset = br.ReadInt32 (); // 36
+                       int userSidLength = br.ReadInt32 (); // 40
+                       int userSidOffset = br.ReadInt32 (); // 44
+                       int dataLength = br.ReadInt32 (); // 48
+                       int dataOffset = br.ReadInt32 (); // 52
+
+                       DateTime timeGenerated = new DateTime (1970, 1, 1).AddSeconds (
+                               timeGeneratedSeconds);
+
+                       DateTime timeWritten = new DateTime (1970, 1, 1).AddSeconds (
+                               timeWrittenSeconds);
+
+                       StringBuilder sb = new StringBuilder ();
+                       while (br.PeekChar () != '\0')
+                               sb.Append (br.ReadChar ());
+                       br.ReadChar (); // skip the null-char
+
+                       string sourceName = sb.ToString ();
+
+                       sb.Length = 0;
+                       while (br.PeekChar () != '\0')
+                               sb.Append (br.ReadChar ());
+                       br.ReadChar (); // skip the null-char
+                       string machineName = sb.ToString ();
+
+                       sb.Length = 0;
+                       while (br.PeekChar () != '\0')
+                               sb.Append (br.ReadChar ());
+                       br.ReadChar (); // skip the null-char
+
+                       string userName = null;
+                       if (userSidLength != 0) {
+                               // TODO: lazy init ?
+                               ms.Position = userSidOffset;
+                               byte [] sid = br.ReadBytes (userSidLength);
+                               userName = LookupAccountSid (machineName, sid);
+                       }
 
+                       ms.Position = stringOffset;
+                       string [] replacementStrings = new string [numStrings];
+                       for (int i = 0; i < numStrings; i++) {
                                sb.Length = 0;
                                while (br.PeekChar () != '\0')
                                        sb.Append (br.ReadChar ());
                                br.ReadChar (); // skip the null-char
+                               replacementStrings [i] = sb.ToString ();
+                       }
 
-                               string userName = null;
-                               if (userSidLength != 0) {
-                                       // TODO: lazy init ?
-                                       ms.Position = userSidOffset;
-                                       byte [] sid = br.ReadBytes (userSidLength);
-                                       userName = LookupAccountSid (machineName, sid);
-                               }
-
-                               ms.Position = stringOffset;
-                               string [] replacementStrings = new string [numStrings];
-                               for (int i = 0; i < numStrings; i++) {
-                                       sb.Length = 0;
-                                       while (br.PeekChar () != '\0')
-                                               sb.Append (br.ReadChar ());
-                                       br.ReadChar (); // skip the null-char
-                                       replacementStrings [i] = sb.ToString ();
-                               }
-
-                               byte [] data = new byte [dataLength];
-                               ms.Position = dataOffset;
-                               br.Read (data, 0, dataLength);
+                       byte [] data = new byte [dataLength];
+                       ms.Position = dataOffset;
+                       br.Read (data, 0, dataLength);
 
-                               // TODO: lazy fetch ??
-                               string message = this.FormatMessage (sourceName, instanceID, replacementStrings);
-                               string category = FormatCategory (sourceName, categoryNumber);
+                       // TODO: lazy fetch ??
+                       string message = this.FormatMessage (sourceName, instanceID, replacementStrings);
+                       string category = FormatCategory (sourceName, categoryNumber);
 
-                               return new EventLogEntry (category, (short) categoryNumber, recordNumber,
-                                       eventID, sourceName, message, userName, machineName,
-                                       (EventLogEntryType) eventType, timeGenerated, timeWritten,
-                                       data, replacementStrings, instanceID);
-                       } finally {
-                               CloseEventLog (hEventLog);
-                       }
+                       return new EventLogEntry (category, (short) categoryNumber, recordNumber,
+                               eventID, sourceName, message, userName, machineName,
+                               (EventLogEntryType) eventType, timeGenerated, timeWritten,
+                               data, replacementStrings, instanceID);
                }
 
                [MonoTODO]
@@ -427,7 +412,6 @@ namespace System.Diagnostics
                                        Environment.SpecialFolder.System), "config");
                                logKey.SetValue ("File", Path.Combine (configPath, file));
                        }
-
                }
 
                private static void UpdateSourceRegistry (RegistryKey sourceKey, EventSourceCreationData data)
@@ -454,6 +438,29 @@ namespace System.Diagnostics
                        return logName.Substring (logName.LastIndexOf ("\\") + 1);
                }
 
+               private void ReadEventLog (int index, byte [] buffer, ref int bytesRead, ref int minBufferNeeded)
+               {
+                       const int max_retries = 3;
+
+                       // if the eventlog file changed since the handle was
+                       // obtained, then we need to re-try multiple times
+                       for (int i = 0; i < max_retries; i++) {
+                               int ret = PInvoke.ReadEventLog (ReadHandle, 
+                                       ReadFlags.Seek | ReadFlags.ForwardsRead,
+                                       index, buffer, buffer.Length, ref bytesRead,
+                                       ref minBufferNeeded);
+                               if (ret != 1) {
+                                       int error = Marshal.GetLastWin32Error ();
+                                       if (i < (max_retries - 1)) {
+                                               CoreEventLog.Reset ();
+                                       } else {
+                                               throw new Win32Exception (error);
+                                       }
+                               }
+                       }
+               }
+
+
                [MonoTODO ("Support remote machines")]
                private static RegistryKey GetEventLogKey (string machineName, bool writable)
                {
@@ -535,17 +542,12 @@ namespace System.Diagnostics
 
                private int OldestEventLogEntry {
                        get {
-                               IntPtr hEventLog = OpenEventLog ();
-                               try {
-                                       int oldestEventLogEntry = 0;
-                                       int ret = PInvoke.GetOldestEventLogRecord (hEventLog, ref oldestEventLogEntry);
-                                       if (ret != 1) {
-                                               throw new Win32Exception (Marshal.GetLastWin32Error ());
-                                       }
-                                       return oldestEventLogEntry;
-                               } finally {
-                                       CloseEventLog (hEventLog);
+                               int oldestEventLogEntry = 0;
+                               int ret = PInvoke.GetOldestEventLogRecord (ReadHandle, ref oldestEventLogEntry);
+                               if (ret != 1) {
+                                       throw new Win32Exception (Marshal.GetLastWin32Error ());
                                }
+                               return oldestEventLogEntry;
                        }
                }
 
@@ -670,18 +672,21 @@ namespace System.Diagnostics
                        return new string [0];
                }
 
-               private IntPtr OpenEventLog ()
-               {
-                       string logName = CoreEventLog.GetLogName ();
-                       IntPtr hEventLog = PInvoke.OpenEventLog (CoreEventLog.MachineName,
-                               logName);
-                       if (hEventLog == IntPtr.Zero) {
-                               throw new InvalidOperationException (string.Format (
-                                       CultureInfo.InvariantCulture, "Event Log '{0}' on computer"
-                                       + " '{1}' cannot be opened.", logName, CoreEventLog.MachineName),
-                                       new Win32Exception ());
+               private IntPtr ReadHandle {
+                       get {
+                               if (_readHandle != IntPtr.Zero)
+                                       return _readHandle;
+
+                               string logName = CoreEventLog.GetLogName ();
+                               _readHandle = PInvoke.OpenEventLog (CoreEventLog.MachineName,
+                                       logName);
+                               if (_readHandle == IntPtr.Zero)
+                                       throw new InvalidOperationException (string.Format (
+                                               CultureInfo.InvariantCulture, "Event Log '{0}' on computer"
+                                               + " '{1}' cannot be opened.", logName, CoreEventLog.MachineName),
+                                               new Win32Exception ());
+                               return _readHandle;
                        }
-                       return hEventLog;
                }
 
                private IntPtr RegisterEventSource ()
@@ -699,12 +704,83 @@ namespace System.Diagnostics
 
                public override void DisableNotification ()
                {
-                       // FIXME: implement
+                       if (_notifyResetEvent != null) {
+                               _notifyResetEvent.Close ();
+                               _notifyResetEvent = null;
+                       }
+
+                       if (_notifyThread != null) {
+                               if (_notifyThread.ThreadState == System.Threading.ThreadState.Running)
+                                       _notifyThread.Abort ();
+                               _notifyThread = null;
+                       }
                }
 
                public override void EnableNotification ()
                {
-                       // FIXME: implement
+                       _notifyResetEvent = new ManualResetEvent (false);
+                       _lastEntryWritten = OldestEventLogEntry + EntryCount;
+                       if (PInvoke.NotifyChangeEventLog (ReadHandle, _notifyResetEvent.Handle) == 0)
+                               throw new InvalidOperationException (string.Format (
+                                       CultureInfo.InvariantCulture, "Unable to receive notifications"
+                                       + " for log '{0}' on computer '{1}'.", CoreEventLog.GetLogName (),
+                                       CoreEventLog.MachineName), new Win32Exception ());
+                       _notifyThread = new Thread (new ThreadStart (NotifyEventThread));
+                       _notifyThread.IsBackground = true;
+                       _notifyThread.Start ();
+               }
+
+               private void NotifyEventThread ()
+               {
+                       while (true) {
+                               _notifyResetEvent.WaitOne ();
+                               lock (this) {
+                                       // after a clear, we something get notified
+                                       // twice for the same entry
+                                       if (_notifying)
+                                               return;
+                                       _notifying = true;
+                               }
+
+                               try {
+                                       int oldest_entry = OldestEventLogEntry;
+                                       if (_lastEntryWritten < oldest_entry)
+                                               _lastEntryWritten = oldest_entry;
+                                       int current_entry = _lastEntryWritten - oldest_entry;
+                                       int last_entry = EntryCount + oldest_entry;
+                                       for (int i = current_entry; i < (last_entry - 1); i++) {
+                                               EventLogEntry entry = GetEntry (i);
+                                               CoreEventLog.OnEntryWritten (entry);
+                                       }
+                                       _lastEntryWritten = last_entry;
+                               } finally {
+                                       lock (this)
+                                               _notifying = false;
+                               }
+                       }
+               }
+
+               public override OverflowAction OverflowAction {
+                       get { throw new NotImplementedException (); }
+               }
+
+               public override int MinimumRetentionDays {
+                       get { throw new NotImplementedException (); }
+               }
+
+               public override long MaximumKilobytes {
+                       get { throw new NotImplementedException (); }
+                       set { throw new NotImplementedException (); }
+               }
+
+               public override void ModifyOverflowPolicy (OverflowAction action, int retentionDays)
+               {
+                       throw new NotImplementedException ();
+               }
+
+               public override void RegisterDisplayName (string resourceFile, long resourceId)
+               {
+                       throw new NotImplementedException ();
                }
 
                private class PInvoke
@@ -718,10 +794,10 @@ namespace System.Diagnostics
                        [DllImport ("advapi32.dll", SetLastError=true)]
                        public static extern int DeregisterEventSource (IntPtr hEventLog);
 
-                       [DllImport ("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)]
+                       [DllImport ("kernel32", CharSet=CharSet.Auto, SetLastError=true)]
                        public static extern int FormatMessage (FormatMessageFlags dwFlags, IntPtr lpSource, uint dwMessageId, int dwLanguageId, ref IntPtr lpBuffer, int nSize, IntPtr [] arguments);
 
-                       [DllImport ("kernel32.dll", SetLastError=true)]
+                       [DllImport ("kernel32", SetLastError=true)]
                        public static extern bool FreeLibrary (IntPtr hModule);
 
                        [DllImport ("advapi32.dll", SetLastError=true)]
@@ -730,10 +806,10 @@ namespace System.Diagnostics
                        [DllImport ("advapi32.dll", SetLastError=true)]
                        public static extern int GetOldestEventLogRecord (IntPtr hEventLog, ref int OldestRecord);
 
-                       [DllImport ("kernel32.dll", SetLastError=true)]
+                       [DllImport ("kernel32", SetLastError=true)]
                        public static extern IntPtr LoadLibraryEx (string lpFileName, IntPtr hFile, LoadFlags dwFlags);
 
-                       [DllImport ("kernel32.dll", SetLastError=true)]
+                       [DllImport ("kernel32", SetLastError=true)]
                        public static extern IntPtr LocalFree (IntPtr hMem);
 
                        [DllImport ("advapi32.dll", SetLastError=true)]
@@ -746,21 +822,25 @@ namespace System.Diagnostics
                                ref uint cchReferencedDomainName,
                                out SidNameUse peUse);
 
+                       [DllImport ("advapi32.dll", SetLastError = true)]
+                       public static extern int NotifyChangeEventLog (IntPtr hEventLog, IntPtr hEvent);
+
                        [DllImport ("advapi32.dll", SetLastError=true)]
                        public static extern IntPtr OpenEventLog (string machineName, string logName);
 
                        [DllImport ("advapi32.dll", SetLastError=true)]
                        public static extern IntPtr RegisterEventSource (string machineName, string sourceName);
 
-                       [DllImport ("Advapi32.dll", SetLastError=true)]
+                       [DllImport ("advapi32.dll", SetLastError=true)]
                        public static extern int ReportEvent (IntPtr hHandle, ushort wType,
                                ushort wCategory, uint dwEventID, IntPtr sid, ushort wNumStrings,
                                uint dwDataSize, string [] lpStrings, byte [] lpRawData);
 
-                       [DllImport ("advapi32.dll", SetLastError = true)]
+                       [DllImport ("advapi32.dll", SetLastError=true)]
                        public static extern int ReadEventLog (IntPtr hEventLog, ReadFlags dwReadFlags, int dwRecordOffset, byte [] buffer, int nNumberOfBytesToRead, ref int pnBytesRead, ref int pnMinNumberOfBytesNeeded);
 
                        public const int ERROR_INSUFFICIENT_BUFFER = 122;
+                       public const int ERROR_EVENTLOG_FILE_CHANGED = 1503;
                }
 
                private enum ReadFlags