Merge pull request #495 from nicolas-raoul/fix-for-issue2907-with-no-formatting-changes
[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 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
17 // 
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
20 // 
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 //
29
30 using System;
31 using System.Collections;
32 using System.ComponentModel;
33 using System.Diagnostics;
34 using System.Globalization;
35 using System.IO;
36 using System.Runtime.InteropServices;
37 using System.Text;
38 using System.Threading;
39
40 using Microsoft.Win32;
41
42 namespace System.Diagnostics
43 {
44         internal class Win32EventLog : EventLogImpl
45         {
46                 private const int MESSAGE_NOT_FOUND = 317;
47                 private ManualResetEvent _notifyResetEvent;
48                 private IntPtr _readHandle;
49                 private Thread _notifyThread;
50                 private int _lastEntryWritten;
51                 private bool _notifying;
52
53                 public Win32EventLog (EventLog coreEventLog)
54                         : base (coreEventLog)
55                 {
56                 }
57
58                 public override void BeginInit ()
59                 {
60                 }
61
62                 public override void Clear ()
63                 {
64                         int ret = PInvoke.ClearEventLog (ReadHandle, null);
65                         if (ret != 1)
66                                 throw new Win32Exception (Marshal.GetLastWin32Error ());
67                 }
68
69                 public override void Close ()
70                 {
71                         if (_readHandle != IntPtr.Zero) {
72                                 CloseEventLog (_readHandle);
73                                 _readHandle = IntPtr.Zero;
74                         }
75                 }
76
77                 public override void CreateEventSource (EventSourceCreationData sourceData)
78                 {
79                         using (RegistryKey eventLogKey = GetEventLogKey (sourceData.MachineName, true)) {
80                                 if (eventLogKey == null)
81                                         throw new InvalidOperationException ("EventLog registry key is missing.");
82
83                                 bool logKeyCreated = false;
84                                 RegistryKey logKey = null;
85                                 try {
86                                         logKey = eventLogKey.OpenSubKey (sourceData.LogName, true);
87                                         if (logKey == null) {
88                                                 ValidateCustomerLogName (sourceData.LogName, 
89                                                         sourceData.MachineName);
90
91                                                 logKey = eventLogKey.CreateSubKey (sourceData.LogName);
92                                                 logKey.SetValue ("Sources", new string [] { sourceData.LogName,
93                                                         sourceData.Source });
94                                                 UpdateLogRegistry (logKey);
95
96                                                 using (RegistryKey sourceKey = logKey.CreateSubKey (sourceData.LogName)) {
97                                                         UpdateSourceRegistry (sourceKey, sourceData);
98                                                 }
99
100                                                 logKeyCreated = true;
101                                         }
102
103                                         if (sourceData.LogName != sourceData.Source) {
104                                                 if (!logKeyCreated) {
105                                                         string [] sources = (string []) logKey.GetValue ("Sources");
106                                                         if (sources == null) {
107                                                                 logKey.SetValue ("Sources", new string [] { sourceData.LogName,
108                                                                         sourceData.Source });
109                                                         } else {
110                                                                 bool found = false;
111                                                                 for (int i = 0; i < sources.Length; i++) {
112                                                                         if (sources [i] == sourceData.Source) {
113                                                                                 found = true;
114                                                                                 break;
115                                                                         }
116                                                                 }
117                                                                 if (!found) {
118                                                                         string [] newSources = new string [sources.Length + 1];
119                                                                         Array.Copy (sources, 0, newSources, 0, sources.Length);
120                                                                         newSources [sources.Length] = sourceData.Source;
121                                                                         logKey.SetValue ("Sources", newSources);
122                                                                 }
123                                                         }
124                                                 }
125                                                 using (RegistryKey sourceKey = logKey.CreateSubKey (sourceData.Source)) {
126                                                         UpdateSourceRegistry (sourceKey, sourceData);
127                                                 }
128                                         }
129                                 } finally {
130                                         if (logKey != null)
131                                                 logKey.Close ();
132                                 }
133                         }
134                 }
135
136                 public override void Delete (string logName, string machineName)
137                 {
138                         using (RegistryKey eventLogKey = GetEventLogKey (machineName, true)) {
139                                 if (eventLogKey == null)
140                                         throw new InvalidOperationException ("The event log key does not exist.");
141
142                                 using (RegistryKey logKey = eventLogKey.OpenSubKey (logName, false)) {
143                                         if (logKey == null)
144                                                 throw new InvalidOperationException (string.Format (
145                                                         CultureInfo.InvariantCulture, "Event Log '{0}'"
146                                                         + " does not exist on computer '{1}'.", logName,
147                                                         machineName));
148
149                                         // remove all eventlog entries for specified log
150                                         CoreEventLog.Clear ();
151
152                                         // remove file holding event log entries
153                                         string file = (string) logKey.GetValue ("File");
154                                         if (file != null) {
155                                                 try {
156                                                         File.Delete (file);
157                                                 } catch (Exception) {
158                                                         // .NET seems to ignore failures here
159                                                 }
160                                         }
161                                 }
162
163                                 eventLogKey.DeleteSubKeyTree (logName);
164                         }
165                 }
166
167                 public override void DeleteEventSource (string source, string machineName)
168                 {
169                         using (RegistryKey logKey = FindLogKeyBySource (source, machineName, true)) {
170                                 if (logKey == null) {
171                                         throw new ArgumentException (string.Format (
172                                                 CultureInfo.InvariantCulture, "The source '{0}' is not"
173                                                 + " registered on computer '{1}'.", source, machineName));
174                                 }
175
176                                 logKey.DeleteSubKeyTree (source);
177
178                                 string [] sources = (string []) logKey.GetValue ("Sources");
179                                 if (sources != null) {
180                                         ArrayList temp = new ArrayList ();
181                                         for (int i = 0; i < sources.Length; i++)
182                                                 if (sources [i] != source)
183                                                         temp.Add (sources [i]);
184                                         string [] newSources = new string [temp.Count];
185                                         temp.CopyTo (newSources, 0);
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                         if (_notifyResetEvent != null) {
708                                 _notifyResetEvent.Close ();
709                                 _notifyResetEvent = null;
710                         }
711
712                         if (_notifyThread != null) {
713                                 if (_notifyThread.ThreadState == System.Threading.ThreadState.Running)
714                                         _notifyThread.Abort ();
715                                 _notifyThread = null;
716                         }
717                 }
718
719                 public override void EnableNotification ()
720                 {
721                         _notifyResetEvent = new ManualResetEvent (false);
722                         _lastEntryWritten = OldestEventLogEntry + EntryCount;
723                         if (PInvoke.NotifyChangeEventLog (ReadHandle, _notifyResetEvent.Handle) == 0)
724                                 throw new InvalidOperationException (string.Format (
725                                         CultureInfo.InvariantCulture, "Unable to receive notifications"
726                                         + " for log '{0}' on computer '{1}'.", CoreEventLog.GetLogName (),
727                                         CoreEventLog.MachineName), new Win32Exception ());
728                         _notifyThread = new Thread (new ThreadStart (NotifyEventThread));
729                         _notifyThread.IsBackground = true;
730                         _notifyThread.Start ();
731                 }
732
733                 private void NotifyEventThread ()
734                 {
735                         while (true) {
736                                 _notifyResetEvent.WaitOne ();
737                                 lock (this) {
738                                         // after a clear, we something get notified
739                                         // twice for the same entry
740                                         if (_notifying)
741                                                 return;
742                                         _notifying = true;
743                                 }
744
745                                 try {
746                                         int oldest_entry = OldestEventLogEntry;
747                                         if (_lastEntryWritten < oldest_entry)
748                                                 _lastEntryWritten = oldest_entry;
749                                         int current_entry = _lastEntryWritten - oldest_entry;
750                                         int last_entry = EntryCount + oldest_entry;
751                                         for (int i = current_entry; i < (last_entry - 1); i++) {
752                                                 EventLogEntry entry = GetEntry (i);
753                                                 CoreEventLog.OnEntryWritten (entry);
754                                         }
755                                         _lastEntryWritten = last_entry;
756                                 } finally {
757                                         lock (this)
758                                                 _notifying = false;
759                                 }
760                         }
761                 }
762
763                 public override OverflowAction OverflowAction {
764                         get { throw new NotImplementedException (); }
765                 }
766
767                 public override int MinimumRetentionDays {
768                         get { throw new NotImplementedException (); }
769                 }
770
771                 public override long MaximumKilobytes {
772                         get { throw new NotImplementedException (); }
773                         set { throw new NotImplementedException (); }
774                 }
775
776                 public override void ModifyOverflowPolicy (OverflowAction action, int retentionDays)
777                 {
778                         throw new NotImplementedException ();
779                 }
780
781                 public override void RegisterDisplayName (string resourceFile, long resourceId)
782                 {
783                         throw new NotImplementedException ();
784                 }
785
786                 private class PInvoke
787                 {
788                         [DllImport ("advapi32.dll", SetLastError=true)]
789                         public static extern int ClearEventLog (IntPtr hEventLog, string lpBackupFileName);
790
791                         [DllImport ("advapi32.dll", SetLastError=true)]
792                         public static extern int CloseEventLog (IntPtr hEventLog);
793
794                         [DllImport ("advapi32.dll", SetLastError=true)]
795                         public static extern int DeregisterEventSource (IntPtr hEventLog);
796
797                         [DllImport ("kernel32", CharSet=CharSet.Auto, SetLastError=true)]
798                         public static extern int FormatMessage (FormatMessageFlags dwFlags, IntPtr lpSource, uint dwMessageId, int dwLanguageId, ref IntPtr lpBuffer, int nSize, IntPtr [] arguments);
799
800                         [DllImport ("kernel32", SetLastError=true)]
801                         public static extern bool FreeLibrary (IntPtr hModule);
802
803                         [DllImport ("advapi32.dll", SetLastError=true)]
804                         public static extern int GetNumberOfEventLogRecords (IntPtr hEventLog, ref int NumberOfRecords);
805
806                         [DllImport ("advapi32.dll", SetLastError=true)]
807                         public static extern int GetOldestEventLogRecord (IntPtr hEventLog, ref int OldestRecord);
808
809                         [DllImport ("kernel32", SetLastError=true)]
810                         public static extern IntPtr LoadLibraryEx (string lpFileName, IntPtr hFile, LoadFlags dwFlags);
811
812                         [DllImport ("kernel32", SetLastError=true)]
813                         public static extern IntPtr LocalFree (IntPtr hMem);
814
815                         [DllImport ("advapi32.dll", SetLastError=true)]
816                         public static extern bool LookupAccountSid (
817                                 string lpSystemName,
818                                 [MarshalAs (UnmanagedType.LPArray)] byte [] Sid,
819                                 StringBuilder lpName,
820                                 ref uint cchName,
821                                 StringBuilder ReferencedDomainName,
822                                 ref uint cchReferencedDomainName,
823                                 out SidNameUse peUse);
824
825                         [DllImport ("advapi32.dll", SetLastError = true)]
826                         public static extern int NotifyChangeEventLog (IntPtr hEventLog, IntPtr hEvent);
827
828                         [DllImport ("advapi32.dll", SetLastError=true)]
829                         public static extern IntPtr OpenEventLog (string machineName, string logName);
830
831                         [DllImport ("advapi32.dll", SetLastError=true)]
832                         public static extern IntPtr RegisterEventSource (string machineName, string sourceName);
833
834                         [DllImport ("advapi32.dll", SetLastError=true)]
835                         public static extern int ReportEvent (IntPtr hHandle, ushort wType,
836                                 ushort wCategory, uint dwEventID, IntPtr sid, ushort wNumStrings,
837                                 uint dwDataSize, string [] lpStrings, byte [] lpRawData);
838
839                         [DllImport ("advapi32.dll", SetLastError=true)]
840                         public static extern int ReadEventLog (IntPtr hEventLog, ReadFlags dwReadFlags, int dwRecordOffset, byte [] buffer, int nNumberOfBytesToRead, ref int pnBytesRead, ref int pnMinNumberOfBytesNeeded);
841
842                         public const int ERROR_INSUFFICIENT_BUFFER = 122;
843                         public const int ERROR_EVENTLOG_FILE_CHANGED = 1503;
844                 }
845
846                 private enum ReadFlags
847                 {
848                         Sequential = 0x001,
849                         Seek = 0x002,
850                         ForwardsRead = 0x004,
851                         BackwardsRead = 0x008
852                 }
853
854                 private enum LoadFlags: uint
855                 {
856                         LibraryAsDataFile = 0x002
857                 }
858
859                 [Flags]
860                 private enum FormatMessageFlags
861                 {
862                         AllocateBuffer = 0x100,
863                         IgnoreInserts = 0x200,
864                         FromHModule = 0x0800,
865                         FromSystem = 0x1000,
866                         ArgumentArray = 0x2000
867                 }
868
869                 private enum SidNameUse
870                 {
871                         User = 1,
872                         Group,
873                         Domain,
874                         lias,
875                         WellKnownGroup,
876                         DeletedAccount,
877                         Invalid,
878                         Unknown,
879                         Computer
880                 }
881         }
882 }
883
884 // http://msdn.microsoft.com/library/en-us/eventlog/base/eventlogrecord_str.asp:
885 //
886 // struct EVENTLOGRECORD {
887 //      int Length;
888 //      int Reserved;
889 //      int RecordNumber;
890 //      int TimeGenerated;
891 //      int TimeWritten;
892 //      int EventID;
893 //      short EventType;
894 //      short NumStrings;
895 //      short EventCategory;
896 //      short ReservedFlags;
897 //      int ClosingRecordNumber;
898 //      int StringOffset;
899 //      int UserSidLength;
900 //      int UserSidOffset;
901 //      int DataLength;
902 //      int DataOffset;
903 // }
904 //
905 // http://www.whitehats.ca/main/members/Malik/malik_eventlogs/malik_eventlogs.html