Merge pull request #3769 from evincarofautumn/fix-verify-before-allocs
[mono.git] / mcs / class / System / System.Diagnostics / Win32EventLog.cs
1 //
2 // System.Diagnostics.Win32EventLog.cs
3 //
4 // Author:
5 //      Gert Driesen <driesen@users.sourceforge.net>
6 //
7 // Copyright (C) 2006 Novell, Inc (http://www.novell.com)
8 //
9 //
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28
29 using System;
30 using System.Collections.Generic;
31 using System.ComponentModel;
32 using System.Diagnostics;
33 using System.Globalization;
34 using System.IO;
35 using System.Runtime.InteropServices;
36 using System.Text;
37 using System.Threading;
38
39 using Microsoft.Win32;
40
41 namespace System.Diagnostics
42 {
43         internal class Win32EventLog : EventLogImpl
44         {
45                 private const int MESSAGE_NOT_FOUND = 317;
46                 private ManualResetEvent _notifyResetEvent;
47                 private IntPtr _readHandle;
48                 private Thread _notifyThread;
49                 private int _lastEntryWritten;
50                 private Object _eventLock = new object();
51
52                 public Win32EventLog (EventLog coreEventLog)
53                         : base (coreEventLog)
54                 {
55                 }
56
57                 public override void BeginInit ()
58                 {
59                 }
60
61                 public override void Clear ()
62                 {
63                         int ret = PInvoke.ClearEventLog (ReadHandle, null);
64                         if (ret != 1)
65                                 throw new Win32Exception (Marshal.GetLastWin32Error ());
66                 }
67
68                 public override void Close ()
69                 {
70                         lock (_eventLock) {
71                                 if (_readHandle != IntPtr.Zero) {
72                                         CloseEventLog (_readHandle);
73                                         _readHandle = IntPtr.Zero;
74                                 }
75                         }
76                 }
77
78                 public override void CreateEventSource (EventSourceCreationData sourceData)
79                 {
80                         using (RegistryKey eventLogKey = GetEventLogKey (sourceData.MachineName, true)) {
81                                 if (eventLogKey == null)
82                                         throw new InvalidOperationException ("EventLog registry key is missing.");
83
84                                 bool logKeyCreated = false;
85                                 RegistryKey logKey = null;
86                                 try {
87                                         logKey = eventLogKey.OpenSubKey (sourceData.LogName, true);
88                                         if (logKey == null) {
89                                                 ValidateCustomerLogName (sourceData.LogName, 
90                                                         sourceData.MachineName);
91
92                                                 logKey = eventLogKey.CreateSubKey (sourceData.LogName);
93                                                 logKey.SetValue ("Sources", new string [] { sourceData.LogName,
94                                                         sourceData.Source });
95                                                 UpdateLogRegistry (logKey);
96
97                                                 using (RegistryKey sourceKey = logKey.CreateSubKey (sourceData.LogName)) {
98                                                         UpdateSourceRegistry (sourceKey, sourceData);
99                                                 }
100
101                                                 logKeyCreated = true;
102                                         }
103
104                                         if (sourceData.LogName != sourceData.Source) {
105                                                 if (!logKeyCreated) {
106                                                         string [] sources = (string []) logKey.GetValue ("Sources");
107                                                         if (sources == null) {
108                                                                 logKey.SetValue ("Sources", new string [] { sourceData.LogName,
109                                                                         sourceData.Source });
110                                                         } else {
111                                                                 bool found = false;
112                                                                 for (int i = 0; i < sources.Length; i++) {
113                                                                         if (sources [i] == sourceData.Source) {
114                                                                                 found = true;
115                                                                                 break;
116                                                                         }
117                                                                 }
118                                                                 if (!found) {
119                                                                         string [] newSources = new string [sources.Length + 1];
120                                                                         Array.Copy (sources, 0, newSources, 0, sources.Length);
121                                                                         newSources [sources.Length] = sourceData.Source;
122                                                                         logKey.SetValue ("Sources", newSources);
123                                                                 }
124                                                         }
125                                                 }
126                                                 using (RegistryKey sourceKey = logKey.CreateSubKey (sourceData.Source)) {
127                                                         UpdateSourceRegistry (sourceKey, sourceData);
128                                                 }
129                                         }
130                                 } finally {
131                                         if (logKey != null)
132                                                 logKey.Close ();
133                                 }
134                         }
135                 }
136
137                 public override void Delete (string logName, string machineName)
138                 {
139                         using (RegistryKey eventLogKey = GetEventLogKey (machineName, true)) {
140                                 if (eventLogKey == null)
141                                         throw new InvalidOperationException ("The event log key does not exist.");
142
143                                 using (RegistryKey logKey = eventLogKey.OpenSubKey (logName, false)) {
144                                         if (logKey == null)
145                                                 throw new InvalidOperationException (string.Format (
146                                                         CultureInfo.InvariantCulture, "Event Log '{0}'"
147                                                         + " does not exist on computer '{1}'.", logName,
148                                                         machineName));
149
150                                         // remove all eventlog entries for specified log
151                                         CoreEventLog.Clear ();
152
153                                         // remove file holding event log entries
154                                         string file = (string) logKey.GetValue ("File");
155                                         if (file != null) {
156                                                 try {
157                                                         File.Delete (file);
158                                                 } catch (Exception) {
159                                                         // .NET seems to ignore failures here
160                                                 }
161                                         }
162                                 }
163
164                                 eventLogKey.DeleteSubKeyTree (logName);
165                         }
166                 }
167
168                 public override void DeleteEventSource (string source, string machineName)
169                 {
170                         using (RegistryKey logKey = FindLogKeyBySource (source, machineName, true)) {
171                                 if (logKey == null) {
172                                         throw new ArgumentException (string.Format (
173                                                 CultureInfo.InvariantCulture, "The source '{0}' is not"
174                                                 + " registered on computer '{1}'.", source, machineName));
175                                 }
176
177                                 logKey.DeleteSubKeyTree (source);
178
179                                 string [] sources = (string []) logKey.GetValue ("Sources");
180                                 if (sources != null) {
181                                         var temp = new List<string> ();
182                                         for (int i = 0; i < sources.Length; i++)
183                                                 if (sources [i] != source)
184                                                         temp.Add (sources [i]);
185                                         string [] newSources = temp.ToArray ();
186                                         logKey.SetValue ("Sources", newSources);
187                                 }
188                         }
189                 }
190
191                 public override void Dispose (bool disposing)
192                 {
193                         Close ();
194                 }
195
196                 public override void EndInit ()
197                 {
198                 }
199
200                 public override bool Exists (string logName, string machineName)
201                 {
202                         using (RegistryKey logKey = FindLogKeyByName (logName, machineName, false)) {
203                                 return (logKey != null);
204                         }
205                 }
206
207                 [MonoTODO] // ParameterResourceFile ??
208                 protected override string FormatMessage (string source, uint messageID, string [] replacementStrings)
209                 {
210                         string formattedMessage = null;
211
212                         string [] msgResDlls = GetMessageResourceDlls (source, "EventMessageFile");
213                         for (int i = 0; i < msgResDlls.Length; i++) {
214                                 formattedMessage = FetchMessage (msgResDlls [i],
215                                         messageID, replacementStrings);
216                                 if (formattedMessage != null)
217                                         break;
218                         }
219
220                         return formattedMessage != null ? formattedMessage : string.Join (
221                                 ", ", replacementStrings);
222                 }
223
224                 private string FormatCategory (string source, int category)
225                 {
226                         string formattedCategory = null;
227
228                         string [] msgResDlls = GetMessageResourceDlls (source, "CategoryMessageFile");
229                         for (int i = 0; i < msgResDlls.Length; i++) {
230                                 formattedCategory = FetchMessage (msgResDlls [i],
231                                         (uint) category, new string [0]);
232                                 if (formattedCategory != null)
233                                         break;
234                         }
235
236                         return formattedCategory != null ? formattedCategory : "(" +
237                                 category.ToString (CultureInfo.InvariantCulture) + ")";
238                 }
239
240                 protected override int GetEntryCount ()
241                 {
242                         int entryCount = 0;
243                         int retVal = PInvoke.GetNumberOfEventLogRecords (ReadHandle, ref entryCount);
244                         if (retVal != 1)
245                                 throw new Win32Exception (Marshal.GetLastWin32Error ());
246                         return entryCount;
247                 }
248
249                 protected override EventLogEntry GetEntry (int index)
250                 {
251                         // http://msdn.microsoft.com/library/en-us/eventlog/base/readeventlog.asp
252                         // http://msdn.microsoft.com/library/en-us/eventlog/base/eventlogrecord_str.asp
253                         // http://www.whitehats.ca/main/members/Malik/malik_eventlogs/malik_eventlogs.html
254
255                         index += OldestEventLogEntry;
256
257                         int bytesRead = 0;
258                         int minBufferNeeded = 0;
259                         byte [] buffer = new byte [0x7ffff]; // according to MSDN this is the max size of the buffer
260
261                         ReadEventLog (index, buffer, ref bytesRead, ref minBufferNeeded);
262
263                         MemoryStream ms = new MemoryStream (buffer);
264                         BinaryReader br = new BinaryReader (ms);
265
266                         // skip first 8 bytes
267                         br.ReadBytes (8);
268
269                         int recordNumber = br.ReadInt32 (); // 8
270
271                         int timeGeneratedSeconds = br.ReadInt32 (); // 12
272                         int timeWrittenSeconds = br.ReadInt32 (); // 16
273                         uint instanceID = br.ReadUInt32 ();
274                         int eventID = EventLog.GetEventID (instanceID);
275                         short eventType = br.ReadInt16 (); // 24
276                         short numStrings = br.ReadInt16 (); ; // 26
277                         short categoryNumber = br.ReadInt16 (); ; // 28
278                         // skip reservedFlags
279                         br.ReadInt16 (); // 30
280                         // skip closingRecordNumber
281                         br.ReadInt32 (); // 32
282                         int stringOffset = br.ReadInt32 (); // 36
283                         int userSidLength = br.ReadInt32 (); // 40
284                         int userSidOffset = br.ReadInt32 (); // 44
285                         int dataLength = br.ReadInt32 (); // 48
286                         int dataOffset = br.ReadInt32 (); // 52
287
288                         DateTime timeGenerated = new DateTime (1970, 1, 1).AddSeconds (
289                                 timeGeneratedSeconds);
290
291                         DateTime timeWritten = new DateTime (1970, 1, 1).AddSeconds (
292                                 timeWrittenSeconds);
293
294                         StringBuilder sb = new StringBuilder ();
295                         while (br.PeekChar () != '\0')
296                                 sb.Append (br.ReadChar ());
297                         br.ReadChar (); // skip the null-char
298
299                         string sourceName = sb.ToString ();
300
301                         sb.Length = 0;
302                         while (br.PeekChar () != '\0')
303                                 sb.Append (br.ReadChar ());
304                         br.ReadChar (); // skip the null-char
305                         string machineName = sb.ToString ();
306
307                         sb.Length = 0;
308                         while (br.PeekChar () != '\0')
309                                 sb.Append (br.ReadChar ());
310                         br.ReadChar (); // skip the null-char
311
312                         string userName = null;
313                         if (userSidLength != 0) {
314                                 // TODO: lazy init ?
315                                 ms.Position = userSidOffset;
316                                 byte [] sid = br.ReadBytes (userSidLength);
317                                 userName = LookupAccountSid (machineName, sid);
318                         }
319
320                         ms.Position = stringOffset;
321                         string [] replacementStrings = new string [numStrings];
322                         for (int i = 0; i < numStrings; i++) {
323                                 sb.Length = 0;
324                                 while (br.PeekChar () != '\0')
325                                         sb.Append (br.ReadChar ());
326                                 br.ReadChar (); // skip the null-char
327                                 replacementStrings [i] = sb.ToString ();
328                         }
329
330                         byte [] data = new byte [dataLength];
331                         ms.Position = dataOffset;
332                         br.Read (data, 0, dataLength);
333
334                         // TODO: lazy fetch ??
335                         string message = this.FormatMessage (sourceName, instanceID, replacementStrings);
336                         string category = FormatCategory (sourceName, categoryNumber);
337
338                         return new EventLogEntry (category, (short) categoryNumber, recordNumber,
339                                 eventID, sourceName, message, userName, machineName,
340                                 (EventLogEntryType) eventType, timeGenerated, timeWritten,
341                                 data, replacementStrings, instanceID);
342                 }
343
344                 [MonoTODO]
345                 protected override string GetLogDisplayName ()
346                 {
347                         return CoreEventLog.Log;
348                 }
349
350                 protected override string [] GetLogNames (string machineName)
351                 {
352                         using (RegistryKey eventLogKey = GetEventLogKey (machineName, true)) {
353                                 if (eventLogKey == null)
354                                         return new string [0];
355
356                                 return eventLogKey.GetSubKeyNames ();
357                         }
358                 }
359
360                 public override string LogNameFromSourceName (string source, string machineName)
361                 {
362                         using (RegistryKey logKey = FindLogKeyBySource (source, machineName, false)) {
363                                 if (logKey == null)
364                                         return string.Empty;
365
366                                 return GetLogName (logKey);
367                         }
368                 }
369
370                 public override bool SourceExists (string source, string machineName)
371                 {
372                         RegistryKey logKey = FindLogKeyBySource (source, machineName, false);
373                         if (logKey != null) {
374                                 logKey.Close ();
375                                 return true;
376                         }
377                         return false;
378                 }
379
380                 public override void WriteEntry (string [] replacementStrings, EventLogEntryType type, uint instanceID, short category, byte [] rawData)
381                 {
382                         IntPtr hEventLog = RegisterEventSource ();
383                         try {
384                                 int ret = PInvoke.ReportEvent (hEventLog, (ushort) type,
385                                         (ushort) category, instanceID, IntPtr.Zero,
386                                         (ushort) replacementStrings.Length,
387                                         (uint) rawData.Length, replacementStrings, rawData);
388                                 if (ret != 1) {
389                                         throw new Win32Exception (Marshal.GetLastWin32Error ());
390                                 }
391                         } finally {
392                                 DeregisterEventSource (hEventLog);
393                         }
394                 }
395
396                 private static void UpdateLogRegistry (RegistryKey logKey)
397                 {
398                         // TODO: write other Log values:
399                         // - MaxSize
400                         // - Retention
401                         // - AutoBackupLogFiles
402
403                         if (logKey.GetValue ("File") == null) {
404                                 string logName = GetLogName (logKey);
405                                 string file;
406                                 if (logName.Length > 8) {
407                                         file = logName.Substring (0, 8) + ".evt";
408                                 } else {
409                                         file = logName + ".evt";
410                                 }
411                                 string configPath = Path.Combine (Environment.GetFolderPath (
412                                         Environment.SpecialFolder.System), "config");
413                                 logKey.SetValue ("File", Path.Combine (configPath, file));
414                         }
415                 }
416
417                 private static void UpdateSourceRegistry (RegistryKey sourceKey, EventSourceCreationData data)
418                 {
419                         if (data.CategoryCount > 0)
420                                 sourceKey.SetValue ("CategoryCount", data.CategoryCount);
421
422                         if (data.CategoryResourceFile != null && data.CategoryResourceFile.Length > 0)
423                                 sourceKey.SetValue ("CategoryMessageFile", data.CategoryResourceFile);
424
425                         if (data.MessageResourceFile != null && data.MessageResourceFile.Length > 0) {
426                                 sourceKey.SetValue ("EventMessageFile", data.MessageResourceFile);
427                         } else {
428                                 // FIXME: write default once we have approval for shipping EventLogMessages.dll
429                         }
430
431                         if (data.ParameterResourceFile != null && data.ParameterResourceFile.Length > 0)
432                                 sourceKey.SetValue ("ParameterMessageFile", data.ParameterResourceFile);
433                 }
434
435                 private static string GetLogName (RegistryKey logKey)
436                 {
437                         string logName = logKey.Name;
438                         return logName.Substring (logName.LastIndexOf ("\\") + 1);
439                 }
440
441                 private void ReadEventLog (int index, byte [] buffer, ref int bytesRead, ref int minBufferNeeded)
442                 {
443                         const int max_retries = 3;
444
445                         // if the eventlog file changed since the handle was
446                         // obtained, then we need to re-try multiple times
447                         for (int i = 0; i < max_retries; i++) {
448                                 int ret = PInvoke.ReadEventLog (ReadHandle, 
449                                         ReadFlags.Seek | ReadFlags.ForwardsRead,
450                                         index, buffer, buffer.Length, ref bytesRead,
451                                         ref minBufferNeeded);
452                                 if (ret != 1) {
453                                         int error = Marshal.GetLastWin32Error ();
454                                         if (i < (max_retries - 1)) {
455                                                 CoreEventLog.Reset ();
456                                         } else {
457                                                 throw new Win32Exception (error);
458                                         }
459                                 }
460                         }
461                 }
462
463
464                 [MonoTODO ("Support remote machines")]
465                 private static RegistryKey GetEventLogKey (string machineName, bool writable)
466                 {
467                         return Registry.LocalMachine.OpenSubKey (@"SYSTEM\CurrentControlSet\Services\EventLog", writable);
468                 }
469
470                 private static RegistryKey FindSourceKeyByName (string source, string machineName, bool writable)
471                 {
472                         if (source == null || source.Length == 0)
473                                 return null;
474
475                         RegistryKey eventLogKey = null;
476                         try {
477                                 eventLogKey = GetEventLogKey (machineName, writable);
478                                 if (eventLogKey == null)
479                                         return null;
480
481                                 string [] subKeys = eventLogKey.GetSubKeyNames ();
482                                 for (int i = 0; i < subKeys.Length; i++) {
483                                         using (RegistryKey logKey = eventLogKey.OpenSubKey (subKeys [i], writable)) {
484                                                 if (logKey == null)
485                                                         break;
486
487                                                 RegistryKey sourceKey = logKey.OpenSubKey (source, writable);
488                                                 if (sourceKey != null)
489                                                         return sourceKey;
490                                         }
491                                 }
492                                 return null;
493                         } finally {
494                                 if (eventLogKey != null)
495                                         eventLogKey.Close ();
496                         }
497                 }
498
499                 private static RegistryKey FindLogKeyByName (string logName, string machineName, bool writable)
500                 {
501                         using (RegistryKey eventLogKey = GetEventLogKey (machineName, writable)) {
502                                 if (eventLogKey == null) {
503                                         return null;
504                                 }
505
506                                 return eventLogKey.OpenSubKey (logName, writable);
507                         }
508                 }
509
510                 private static RegistryKey FindLogKeyBySource (string source, string machineName, bool writable)
511                 {
512                         if (source == null || source.Length == 0)
513                                 return null;
514
515                         RegistryKey eventLogKey = null;
516                         try {
517                                 eventLogKey = GetEventLogKey (machineName, writable);
518                                 if (eventLogKey == null)
519                                         return null;
520
521                                 string [] subKeys = eventLogKey.GetSubKeyNames ();
522                                 for (int i = 0; i < subKeys.Length; i++) {
523                                         RegistryKey sourceKey = null;
524                                         try {
525                                                 RegistryKey logKey = eventLogKey.OpenSubKey (subKeys [i], writable);
526                                                 if (logKey != null) {
527                                                         sourceKey = logKey.OpenSubKey (source, writable);
528                                                         if (sourceKey != null)
529                                                                 return logKey;
530                                                 }
531                                         } finally {
532                                                 if (sourceKey != null)
533                                                         sourceKey.Close ();
534                                         }
535                                 }
536                                 return null;
537                         } finally {
538                                 if (eventLogKey != null)
539                                         eventLogKey.Close ();
540                         }
541                 }
542
543                 private int OldestEventLogEntry {
544                         get {
545                                 int oldestEventLogEntry = 0;
546                                 int ret = PInvoke.GetOldestEventLogRecord (ReadHandle, ref oldestEventLogEntry);
547                                 if (ret != 1) {
548                                         throw new Win32Exception (Marshal.GetLastWin32Error ());
549                                 }
550                                 return oldestEventLogEntry;
551                         }
552                 }
553
554                 private void CloseEventLog (IntPtr hEventLog)
555                 {
556                         int ret = PInvoke.CloseEventLog (hEventLog);
557                         if (ret != 1) {
558                                 throw new Win32Exception (Marshal.GetLastWin32Error ());
559                         }
560                 }
561
562                 private void DeregisterEventSource (IntPtr hEventLog)
563                 {
564                         int ret = PInvoke.DeregisterEventSource (hEventLog);
565                         if (ret != 1) {
566                                 throw new Win32Exception (Marshal.GetLastWin32Error ());
567                         }
568                 }
569
570                 private static string LookupAccountSid (string machineName, byte [] sid)
571                 {
572                         // http://www.pinvoke.net/default.aspx/advapi32/LookupAccountSid.html
573                         // http://msdn.microsoft.com/library/en-us/secauthz/security/lookupaccountsid.asp
574
575                         StringBuilder name = new StringBuilder ();
576                         uint cchName = (uint) name.Capacity;
577                         StringBuilder referencedDomainName = new StringBuilder ();
578                         uint cchReferencedDomainName = (uint) referencedDomainName.Capacity;
579                         SidNameUse sidUse;
580
581                         string accountName = null;
582
583                         while (accountName == null) {
584                                 bool retOk = PInvoke.LookupAccountSid (machineName, sid, name, ref cchName,
585                                         referencedDomainName, ref cchReferencedDomainName,
586                                         out sidUse);
587                                 if (!retOk) {
588                                         int err = Marshal.GetLastWin32Error ();
589                                         if (err == PInvoke.ERROR_INSUFFICIENT_BUFFER) {
590                                                 name.EnsureCapacity ((int) cchName);
591                                                 referencedDomainName.EnsureCapacity ((int) cchReferencedDomainName);
592                                         } else {
593                                                 // TODO: write warning ?
594                                                 accountName = string.Empty;
595                                         }
596                                 } else {
597                                         accountName = string.Format ("{0}\\{1}", referencedDomainName.ToString (),
598                                                 name.ToString ());
599                                 }
600                         }
601                         return accountName;
602                 }
603
604                 private static string FetchMessage (string msgDll, uint messageID, string [] replacementStrings)
605                 {
606                         // http://msdn.microsoft.com/library/en-us/debug/base/formatmessage.asp
607                         // http://msdn.microsoft.com/msdnmag/issues/02/08/CQA/
608                         // http://msdn.microsoft.com/netframework/programming/netcf/cffaq/default.aspx
609
610                         IntPtr msgDllHandle = PInvoke.LoadLibraryEx (msgDll, IntPtr.Zero,
611                                 LoadFlags.LibraryAsDataFile);
612                         if (msgDllHandle == IntPtr.Zero)
613                                 // TODO: write warning
614                                 return null;
615
616                         IntPtr lpMsgBuf = IntPtr.Zero;
617                         IntPtr [] arguments = new IntPtr [replacementStrings.Length];
618
619                         try {
620                                 for (int i = 0; i < replacementStrings.Length; i++) {
621                                         arguments [i] = Marshal.StringToHGlobalAuto (
622                                                 replacementStrings [i]);
623                                 }
624
625                                 int ret = PInvoke.FormatMessage (FormatMessageFlags.ArgumentArray |
626                                         FormatMessageFlags.FromHModule | FormatMessageFlags.AllocateBuffer,
627                                         msgDllHandle, messageID, 0, ref lpMsgBuf, 0, arguments);
628                                 if (ret != 0) {
629                                         string sRet = Marshal.PtrToStringAuto (lpMsgBuf);
630                                         lpMsgBuf = PInvoke.LocalFree (lpMsgBuf);
631                                         // remove trailing whitespace (CRLF)
632                                         return sRet.TrimEnd (null);
633                                 } else {
634                                         int err = Marshal.GetLastWin32Error ();
635                                         if (err == MESSAGE_NOT_FOUND) {
636                                                 // do not consider this a failure (or even warning) as
637                                                 // multiple message resource DLLs may have been configured
638                                                 // and as such we just need to try the next library if
639                                                 // the current one does not contain a message for this
640                                                 // ID
641                                         } else {
642                                                 // TODO: report warning
643                                         }
644                                 }
645                         } finally {
646                                 // release unmanaged memory allocated for replacement strings
647                                 for (int i = 0; i < arguments.Length; i++) {
648                                         IntPtr argument = arguments [i];
649                                         if (argument != IntPtr.Zero)
650                                                 Marshal.FreeHGlobal (argument);
651                                 }
652
653                                 PInvoke.FreeLibrary (msgDllHandle);
654                         }
655                         return null;
656                 }
657
658                 private string [] GetMessageResourceDlls (string source, string valueName)
659                 {
660                         // Some event sources (such as Userenv) have multiple message
661                         // resource DLLs, delimited by a semicolon.
662
663                         RegistryKey sourceKey = FindSourceKeyByName (source,
664                                 CoreEventLog.MachineName, false);
665                         if (sourceKey != null) {
666                                 string value = sourceKey.GetValue (valueName) as string;
667                                 if (value != null) {
668                                         string [] msgResDlls = value.Split (';');
669                                         return msgResDlls;
670                                 }
671                         }
672                         return new string [0];
673                 }
674
675                 private IntPtr ReadHandle {
676                         get {
677                                 if (_readHandle != IntPtr.Zero)
678                                         return _readHandle;
679
680                                 string logName = CoreEventLog.GetLogName ();
681                                 _readHandle = PInvoke.OpenEventLog (CoreEventLog.MachineName,
682                                         logName);
683                                 if (_readHandle == IntPtr.Zero)
684                                         throw new InvalidOperationException (string.Format (
685                                                 CultureInfo.InvariantCulture, "Event Log '{0}' on computer"
686                                                 + " '{1}' cannot be opened.", logName, CoreEventLog.MachineName),
687                                                 new Win32Exception ());
688                                 return _readHandle;
689                         }
690                 }
691
692                 private IntPtr RegisterEventSource ()
693                 {
694                         IntPtr hEventLog = PInvoke.RegisterEventSource (
695                                 CoreEventLog.MachineName, CoreEventLog.Source);
696                         if (hEventLog == IntPtr.Zero) {
697                                 throw new InvalidOperationException (string.Format (
698                                         CultureInfo.InvariantCulture, "Event source '{0}' on computer"
699                                         + " '{1}' cannot be opened.", CoreEventLog.Source,
700                                         CoreEventLog.MachineName), new Win32Exception ());
701                         }
702                         return hEventLog;
703                 }
704
705                 public override void DisableNotification ()
706                 {
707                         lock (_eventLock) {
708                                 if (_notifyResetEvent != null) {
709                                         _notifyResetEvent.Close ();
710                                         _notifyResetEvent = null;
711                                 }
712                                 _notifyThread = null;
713                         }
714                 }
715
716                 public override void EnableNotification ()
717                 {
718                         lock (_eventLock) {
719                                 if (_notifyResetEvent != null)
720                                         return;
721
722                                 _notifyResetEvent = new ManualResetEvent (false);
723                                 _lastEntryWritten = OldestEventLogEntry + EntryCount;
724                                 if (PInvoke.NotifyChangeEventLog (ReadHandle, _notifyResetEvent.Handle) == 0)
725                                         throw new InvalidOperationException (string.Format (
726                                                 CultureInfo.InvariantCulture, "Unable to receive notifications"
727                                                 + " for log '{0}' on computer '{1}'.", CoreEventLog.GetLogName (),
728                                                 CoreEventLog.MachineName), new Win32Exception ());
729                                 _notifyThread = new Thread (() => NotifyEventThread(_notifyResetEvent));
730                                 _notifyThread.IsBackground = true;
731                                 _notifyThread.Start ();
732                         }
733                 }
734
735                 private void NotifyEventThread (ManualResetEvent resetEvent)
736                 {
737                         while (true) {
738                                 try {
739                                         resetEvent.WaitOne ();
740                                 } catch (ObjectDisposedException) {
741                                         // Notifications have been disabled and event 
742                                         // has been closed but not yet nulled. End thread.
743                                         break;
744                                 }
745
746                                 lock (_eventLock) {
747                                         if (resetEvent != _notifyResetEvent) {
748                                                 // A new thread has started with another reset event instance
749                                                 // or DisableNotifications has been called, setting
750                                                 // _notifyResetEvent to null. In both cases end this thread.
751                                                 break;
752                                         }
753
754                                         if (_readHandle == IntPtr.Zero)
755                                                 break;
756
757                                         int oldest_entry = OldestEventLogEntry;
758                                         if (_lastEntryWritten < oldest_entry)
759                                                 _lastEntryWritten = oldest_entry;
760                                         int current_entry = _lastEntryWritten - oldest_entry;
761                                         int last_entry = EntryCount + oldest_entry;
762                                         for (int i = current_entry; i < (last_entry - 1); i++) {
763                                                 EventLogEntry entry = GetEntry (i);
764                                                 CoreEventLog.OnEntryWritten (entry);
765                                         }
766                                         _lastEntryWritten = last_entry;
767                                 }
768                         }
769                 }
770
771                 public override OverflowAction OverflowAction {
772                         get { throw new NotImplementedException (); }
773                 }
774
775                 public override int MinimumRetentionDays {
776                         get { throw new NotImplementedException (); }
777                 }
778
779                 public override long MaximumKilobytes {
780                         get { throw new NotImplementedException (); }
781                         set { throw new NotImplementedException (); }
782                 }
783
784                 public override void ModifyOverflowPolicy (OverflowAction action, int retentionDays)
785                 {
786                         throw new NotImplementedException ();
787                 }
788
789                 public override void RegisterDisplayName (string resourceFile, long resourceId)
790                 {
791                         throw new NotImplementedException ();
792                 }
793
794                 private class PInvoke
795                 {
796                         [DllImport ("advapi32.dll", SetLastError=true)]
797                         public static extern int ClearEventLog (IntPtr hEventLog, string lpBackupFileName);
798
799                         [DllImport ("advapi32.dll", SetLastError=true)]
800                         public static extern int CloseEventLog (IntPtr hEventLog);
801
802                         [DllImport ("advapi32.dll", SetLastError=true)]
803                         public static extern int DeregisterEventSource (IntPtr hEventLog);
804
805                         [DllImport ("kernel32", CharSet=CharSet.Auto, SetLastError=true)]
806                         public static extern int FormatMessage (FormatMessageFlags dwFlags, IntPtr lpSource, uint dwMessageId, int dwLanguageId, ref IntPtr lpBuffer, int nSize, IntPtr [] arguments);
807
808                         [DllImport ("kernel32", SetLastError=true)]
809                         public static extern bool FreeLibrary (IntPtr hModule);
810
811                         [DllImport ("advapi32.dll", SetLastError=true)]
812                         public static extern int GetNumberOfEventLogRecords (IntPtr hEventLog, ref int NumberOfRecords);
813
814                         [DllImport ("advapi32.dll", SetLastError=true)]
815                         public static extern int GetOldestEventLogRecord (IntPtr hEventLog, ref int OldestRecord);
816
817                         [DllImport ("kernel32", SetLastError=true)]
818                         public static extern IntPtr LoadLibraryEx (string lpFileName, IntPtr hFile, LoadFlags dwFlags);
819
820                         [DllImport ("kernel32", SetLastError=true)]
821                         public static extern IntPtr LocalFree (IntPtr hMem);
822
823                         [DllImport ("advapi32.dll", SetLastError=true)]
824                         public static extern bool LookupAccountSid (
825                                 string lpSystemName,
826                                 [MarshalAs (UnmanagedType.LPArray)] byte [] Sid,
827                                 StringBuilder lpName,
828                                 ref uint cchName,
829                                 StringBuilder ReferencedDomainName,
830                                 ref uint cchReferencedDomainName,
831                                 out SidNameUse peUse);
832
833                         [DllImport ("advapi32.dll", SetLastError = true)]
834                         public static extern int NotifyChangeEventLog (IntPtr hEventLog, IntPtr hEvent);
835
836                         [DllImport ("advapi32.dll", SetLastError=true)]
837                         public static extern IntPtr OpenEventLog (string machineName, string logName);
838
839                         [DllImport ("advapi32.dll", SetLastError=true)]
840                         public static extern IntPtr RegisterEventSource (string machineName, string sourceName);
841
842                         [DllImport ("advapi32.dll", SetLastError=true)]
843                         public static extern int ReportEvent (IntPtr hHandle, ushort wType,
844                                 ushort wCategory, uint dwEventID, IntPtr sid, ushort wNumStrings,
845                                 uint dwDataSize, string [] lpStrings, byte [] lpRawData);
846
847                         [DllImport ("advapi32.dll", SetLastError=true)]
848                         public static extern int ReadEventLog (IntPtr hEventLog, ReadFlags dwReadFlags, int dwRecordOffset, byte [] buffer, int nNumberOfBytesToRead, ref int pnBytesRead, ref int pnMinNumberOfBytesNeeded);
849
850                         public const int ERROR_INSUFFICIENT_BUFFER = 122;
851                         public const int ERROR_EVENTLOG_FILE_CHANGED = 1503;
852                 }
853
854                 private enum ReadFlags
855                 {
856                         Sequential = 0x001,
857                         Seek = 0x002,
858                         ForwardsRead = 0x004,
859                         BackwardsRead = 0x008
860                 }
861
862                 private enum LoadFlags: uint
863                 {
864                         LibraryAsDataFile = 0x002
865                 }
866
867                 [Flags]
868                 private enum FormatMessageFlags
869                 {
870                         AllocateBuffer = 0x100,
871                         IgnoreInserts = 0x200,
872                         FromHModule = 0x0800,
873                         FromSystem = 0x1000,
874                         ArgumentArray = 0x2000
875                 }
876
877                 private enum SidNameUse
878                 {
879                         User = 1,
880                         Group,
881                         Domain,
882                         lias,
883                         WellKnownGroup,
884                         DeletedAccount,
885                         Invalid,
886                         Unknown,
887                         Computer
888                 }
889         }
890 }
891
892 // http://msdn.microsoft.com/library/en-us/eventlog/base/eventlogrecord_str.asp:
893 //
894 // struct EVENTLOGRECORD {
895 //      int Length;
896 //      int Reserved;
897 //      int RecordNumber;
898 //      int TimeGenerated;
899 //      int TimeWritten;
900 //      int EventID;
901 //      short EventType;
902 //      short NumStrings;
903 //      short EventCategory;
904 //      short ReservedFlags;
905 //      int ClosingRecordNumber;
906 //      int StringOffset;
907 //      int UserSidLength;
908 //      int UserSidOffset;
909 //      int DataLength;
910 //      int DataOffset;
911 // }
912 //
913 // http://www.whitehats.ca/main/members/Malik/malik_eventlogs/malik_eventlogs.html