Document HTTP_PROXY and NO_PROXY environment variables.
[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.Generic;
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                                         var temp = new List<string> ();
181                                         for (int i = 0; i < sources.Length; i++)
182                                                 if (sources [i] != source)
183                                                         temp.Add (sources [i]);
184                                         string [] newSources = temp.ToArray ();
185                                         logKey.SetValue ("Sources", newSources);
186                                 }
187                         }
188                 }
189
190                 public override void Dispose (bool disposing)
191                 {
192                         Close ();
193                 }
194
195                 public override void EndInit ()
196                 {
197                 }
198
199                 public override bool Exists (string logName, string machineName)
200                 {
201                         using (RegistryKey logKey = FindLogKeyByName (logName, machineName, false)) {
202                                 return (logKey != null);
203                         }
204                 }
205
206                 [MonoTODO] // ParameterResourceFile ??
207                 protected override string FormatMessage (string source, uint messageID, string [] replacementStrings)
208                 {
209                         string formattedMessage = null;
210
211                         string [] msgResDlls = GetMessageResourceDlls (source, "EventMessageFile");
212                         for (int i = 0; i < msgResDlls.Length; i++) {
213                                 formattedMessage = FetchMessage (msgResDlls [i],
214                                         messageID, replacementStrings);
215                                 if (formattedMessage != null)
216                                         break;
217                         }
218
219                         return formattedMessage != null ? formattedMessage : string.Join (
220                                 ", ", replacementStrings);
221                 }
222
223                 private string FormatCategory (string source, int category)
224                 {
225                         string formattedCategory = null;
226
227                         string [] msgResDlls = GetMessageResourceDlls (source, "CategoryMessageFile");
228                         for (int i = 0; i < msgResDlls.Length; i++) {
229                                 formattedCategory = FetchMessage (msgResDlls [i],
230                                         (uint) category, new string [0]);
231                                 if (formattedCategory != null)
232                                         break;
233                         }
234
235                         return formattedCategory != null ? formattedCategory : "(" +
236                                 category.ToString (CultureInfo.InvariantCulture) + ")";
237                 }
238
239                 protected override int GetEntryCount ()
240                 {
241                         int entryCount = 0;
242                         int retVal = PInvoke.GetNumberOfEventLogRecords (ReadHandle, ref entryCount);
243                         if (retVal != 1)
244                                 throw new Win32Exception (Marshal.GetLastWin32Error ());
245                         return entryCount;
246                 }
247
248                 protected override EventLogEntry GetEntry (int index)
249                 {
250                         // http://msdn.microsoft.com/library/en-us/eventlog/base/readeventlog.asp
251                         // http://msdn.microsoft.com/library/en-us/eventlog/base/eventlogrecord_str.asp
252                         // http://www.whitehats.ca/main/members/Malik/malik_eventlogs/malik_eventlogs.html
253
254                         index += OldestEventLogEntry;
255
256                         int bytesRead = 0;
257                         int minBufferNeeded = 0;
258                         byte [] buffer = new byte [0x7ffff]; // according to MSDN this is the max size of the buffer
259
260                         ReadEventLog (index, buffer, ref bytesRead, ref minBufferNeeded);
261
262                         MemoryStream ms = new MemoryStream (buffer);
263                         BinaryReader br = new BinaryReader (ms);
264
265                         // skip first 8 bytes
266                         br.ReadBytes (8);
267
268                         int recordNumber = br.ReadInt32 (); // 8
269
270                         int timeGeneratedSeconds = br.ReadInt32 (); // 12
271                         int timeWrittenSeconds = br.ReadInt32 (); // 16
272                         uint instanceID = br.ReadUInt32 ();
273                         int eventID = EventLog.GetEventID (instanceID);
274                         short eventType = br.ReadInt16 (); // 24
275                         short numStrings = br.ReadInt16 (); ; // 26
276                         short categoryNumber = br.ReadInt16 (); ; // 28
277                         // skip reservedFlags
278                         br.ReadInt16 (); // 30
279                         // skip closingRecordNumber
280                         br.ReadInt32 (); // 32
281                         int stringOffset = br.ReadInt32 (); // 36
282                         int userSidLength = br.ReadInt32 (); // 40
283                         int userSidOffset = br.ReadInt32 (); // 44
284                         int dataLength = br.ReadInt32 (); // 48
285                         int dataOffset = br.ReadInt32 (); // 52
286
287                         DateTime timeGenerated = new DateTime (1970, 1, 1).AddSeconds (
288                                 timeGeneratedSeconds);
289
290                         DateTime timeWritten = new DateTime (1970, 1, 1).AddSeconds (
291                                 timeWrittenSeconds);
292
293                         StringBuilder sb = new StringBuilder ();
294                         while (br.PeekChar () != '\0')
295                                 sb.Append (br.ReadChar ());
296                         br.ReadChar (); // skip the null-char
297
298                         string sourceName = sb.ToString ();
299
300                         sb.Length = 0;
301                         while (br.PeekChar () != '\0')
302                                 sb.Append (br.ReadChar ());
303                         br.ReadChar (); // skip the null-char
304                         string machineName = sb.ToString ();
305
306                         sb.Length = 0;
307                         while (br.PeekChar () != '\0')
308                                 sb.Append (br.ReadChar ());
309                         br.ReadChar (); // skip the null-char
310
311                         string userName = null;
312                         if (userSidLength != 0) {
313                                 // TODO: lazy init ?
314                                 ms.Position = userSidOffset;
315                                 byte [] sid = br.ReadBytes (userSidLength);
316                                 userName = LookupAccountSid (machineName, sid);
317                         }
318
319                         ms.Position = stringOffset;
320                         string [] replacementStrings = new string [numStrings];
321                         for (int i = 0; i < numStrings; i++) {
322                                 sb.Length = 0;
323                                 while (br.PeekChar () != '\0')
324                                         sb.Append (br.ReadChar ());
325                                 br.ReadChar (); // skip the null-char
326                                 replacementStrings [i] = sb.ToString ();
327                         }
328
329                         byte [] data = new byte [dataLength];
330                         ms.Position = dataOffset;
331                         br.Read (data, 0, dataLength);
332
333                         // TODO: lazy fetch ??
334                         string message = this.FormatMessage (sourceName, instanceID, replacementStrings);
335                         string category = FormatCategory (sourceName, categoryNumber);
336
337                         return new EventLogEntry (category, (short) categoryNumber, recordNumber,
338                                 eventID, sourceName, message, userName, machineName,
339                                 (EventLogEntryType) eventType, timeGenerated, timeWritten,
340                                 data, replacementStrings, instanceID);
341                 }
342
343                 [MonoTODO]
344                 protected override string GetLogDisplayName ()
345                 {
346                         return CoreEventLog.Log;
347                 }
348
349                 protected override string [] GetLogNames (string machineName)
350                 {
351                         using (RegistryKey eventLogKey = GetEventLogKey (machineName, true)) {
352                                 if (eventLogKey == null)
353                                         return new string [0];
354
355                                 return eventLogKey.GetSubKeyNames ();
356                         }
357                 }
358
359                 public override string LogNameFromSourceName (string source, string machineName)
360                 {
361                         using (RegistryKey logKey = FindLogKeyBySource (source, machineName, false)) {
362                                 if (logKey == null)
363                                         return string.Empty;
364
365                                 return GetLogName (logKey);
366                         }
367                 }
368
369                 public override bool SourceExists (string source, string machineName)
370                 {
371                         RegistryKey logKey = FindLogKeyBySource (source, machineName, false);
372                         if (logKey != null) {
373                                 logKey.Close ();
374                                 return true;
375                         }
376                         return false;
377                 }
378
379                 public override void WriteEntry (string [] replacementStrings, EventLogEntryType type, uint instanceID, short category, byte [] rawData)
380                 {
381                         IntPtr hEventLog = RegisterEventSource ();
382                         try {
383                                 int ret = PInvoke.ReportEvent (hEventLog, (ushort) type,
384                                         (ushort) category, instanceID, IntPtr.Zero,
385                                         (ushort) replacementStrings.Length,
386                                         (uint) rawData.Length, replacementStrings, rawData);
387                                 if (ret != 1) {
388                                         throw new Win32Exception (Marshal.GetLastWin32Error ());
389                                 }
390                         } finally {
391                                 DeregisterEventSource (hEventLog);
392                         }
393                 }
394
395                 private static void UpdateLogRegistry (RegistryKey logKey)
396                 {
397                         // TODO: write other Log values:
398                         // - MaxSize
399                         // - Retention
400                         // - AutoBackupLogFiles
401
402                         if (logKey.GetValue ("File") == null) {
403                                 string logName = GetLogName (logKey);
404                                 string file;
405                                 if (logName.Length > 8) {
406                                         file = logName.Substring (0, 8) + ".evt";
407                                 } else {
408                                         file = logName + ".evt";
409                                 }
410                                 string configPath = Path.Combine (Environment.GetFolderPath (
411                                         Environment.SpecialFolder.System), "config");
412                                 logKey.SetValue ("File", Path.Combine (configPath, file));
413                         }
414                 }
415
416                 private static void UpdateSourceRegistry (RegistryKey sourceKey, EventSourceCreationData data)
417                 {
418                         if (data.CategoryCount > 0)
419                                 sourceKey.SetValue ("CategoryCount", data.CategoryCount);
420
421                         if (data.CategoryResourceFile != null && data.CategoryResourceFile.Length > 0)
422                                 sourceKey.SetValue ("CategoryMessageFile", data.CategoryResourceFile);
423
424                         if (data.MessageResourceFile != null && data.MessageResourceFile.Length > 0) {
425                                 sourceKey.SetValue ("EventMessageFile", data.MessageResourceFile);
426                         } else {
427                                 // FIXME: write default once we have approval for shipping EventLogMessages.dll
428                         }
429
430                         if (data.ParameterResourceFile != null && data.ParameterResourceFile.Length > 0)
431                                 sourceKey.SetValue ("ParameterMessageFile", data.ParameterResourceFile);
432                 }
433
434                 private static string GetLogName (RegistryKey logKey)
435                 {
436                         string logName = logKey.Name;
437                         return logName.Substring (logName.LastIndexOf ("\\") + 1);
438                 }
439
440                 private void ReadEventLog (int index, byte [] buffer, ref int bytesRead, ref int minBufferNeeded)
441                 {
442                         const int max_retries = 3;
443
444                         // if the eventlog file changed since the handle was
445                         // obtained, then we need to re-try multiple times
446                         for (int i = 0; i < max_retries; i++) {
447                                 int ret = PInvoke.ReadEventLog (ReadHandle, 
448                                         ReadFlags.Seek | ReadFlags.ForwardsRead,
449                                         index, buffer, buffer.Length, ref bytesRead,
450                                         ref minBufferNeeded);
451                                 if (ret != 1) {
452                                         int error = Marshal.GetLastWin32Error ();
453                                         if (i < (max_retries - 1)) {
454                                                 CoreEventLog.Reset ();
455                                         } else {
456                                                 throw new Win32Exception (error);
457                                         }
458                                 }
459                         }
460                 }
461
462
463                 [MonoTODO ("Support remote machines")]
464                 private static RegistryKey GetEventLogKey (string machineName, bool writable)
465                 {
466                         return Registry.LocalMachine.OpenSubKey (@"SYSTEM\CurrentControlSet\Services\EventLog", writable);
467                 }
468
469                 private static RegistryKey FindSourceKeyByName (string source, string machineName, bool writable)
470                 {
471                         if (source == null || source.Length == 0)
472                                 return null;
473
474                         RegistryKey eventLogKey = null;
475                         try {
476                                 eventLogKey = GetEventLogKey (machineName, writable);
477                                 if (eventLogKey == null)
478                                         return null;
479
480                                 string [] subKeys = eventLogKey.GetSubKeyNames ();
481                                 for (int i = 0; i < subKeys.Length; i++) {
482                                         using (RegistryKey logKey = eventLogKey.OpenSubKey (subKeys [i], writable)) {
483                                                 if (logKey == null)
484                                                         break;
485
486                                                 RegistryKey sourceKey = logKey.OpenSubKey (source, writable);
487                                                 if (sourceKey != null)
488                                                         return sourceKey;
489                                         }
490                                 }
491                                 return null;
492                         } finally {
493                                 if (eventLogKey != null)
494                                         eventLogKey.Close ();
495                         }
496                 }
497
498                 private static RegistryKey FindLogKeyByName (string logName, string machineName, bool writable)
499                 {
500                         using (RegistryKey eventLogKey = GetEventLogKey (machineName, writable)) {
501                                 if (eventLogKey == null) {
502                                         return null;
503                                 }
504
505                                 return eventLogKey.OpenSubKey (logName, writable);
506                         }
507                 }
508
509                 private static RegistryKey FindLogKeyBySource (string source, string machineName, bool writable)
510                 {
511                         if (source == null || source.Length == 0)
512                                 return null;
513
514                         RegistryKey eventLogKey = null;
515                         try {
516                                 eventLogKey = GetEventLogKey (machineName, writable);
517                                 if (eventLogKey == null)
518                                         return null;
519
520                                 string [] subKeys = eventLogKey.GetSubKeyNames ();
521                                 for (int i = 0; i < subKeys.Length; i++) {
522                                         RegistryKey sourceKey = null;
523                                         try {
524                                                 RegistryKey logKey = eventLogKey.OpenSubKey (subKeys [i], writable);
525                                                 if (logKey != null) {
526                                                         sourceKey = logKey.OpenSubKey (source, writable);
527                                                         if (sourceKey != null)
528                                                                 return logKey;
529                                                 }
530                                         } finally {
531                                                 if (sourceKey != null)
532                                                         sourceKey.Close ();
533                                         }
534                                 }
535                                 return null;
536                         } finally {
537                                 if (eventLogKey != null)
538                                         eventLogKey.Close ();
539                         }
540                 }
541
542                 private int OldestEventLogEntry {
543                         get {
544                                 int oldestEventLogEntry = 0;
545                                 int ret = PInvoke.GetOldestEventLogRecord (ReadHandle, ref oldestEventLogEntry);
546                                 if (ret != 1) {
547                                         throw new Win32Exception (Marshal.GetLastWin32Error ());
548                                 }
549                                 return oldestEventLogEntry;
550                         }
551                 }
552
553                 private void CloseEventLog (IntPtr hEventLog)
554                 {
555                         int ret = PInvoke.CloseEventLog (hEventLog);
556                         if (ret != 1) {
557                                 throw new Win32Exception (Marshal.GetLastWin32Error ());
558                         }
559                 }
560
561                 private void DeregisterEventSource (IntPtr hEventLog)
562                 {
563                         int ret = PInvoke.DeregisterEventSource (hEventLog);
564                         if (ret != 1) {
565                                 throw new Win32Exception (Marshal.GetLastWin32Error ());
566                         }
567                 }
568
569                 private static string LookupAccountSid (string machineName, byte [] sid)
570                 {
571                         // http://www.pinvoke.net/default.aspx/advapi32/LookupAccountSid.html
572                         // http://msdn.microsoft.com/library/en-us/secauthz/security/lookupaccountsid.asp
573
574                         StringBuilder name = new StringBuilder ();
575                         uint cchName = (uint) name.Capacity;
576                         StringBuilder referencedDomainName = new StringBuilder ();
577                         uint cchReferencedDomainName = (uint) referencedDomainName.Capacity;
578                         SidNameUse sidUse;
579
580                         string accountName = null;
581
582                         while (accountName == null) {
583                                 bool retOk = PInvoke.LookupAccountSid (machineName, sid, name, ref cchName,
584                                         referencedDomainName, ref cchReferencedDomainName,
585                                         out sidUse);
586                                 if (!retOk) {
587                                         int err = Marshal.GetLastWin32Error ();
588                                         if (err == PInvoke.ERROR_INSUFFICIENT_BUFFER) {
589                                                 name.EnsureCapacity ((int) cchName);
590                                                 referencedDomainName.EnsureCapacity ((int) cchReferencedDomainName);
591                                         } else {
592                                                 // TODO: write warning ?
593                                                 accountName = string.Empty;
594                                         }
595                                 } else {
596                                         accountName = string.Format ("{0}\\{1}", referencedDomainName.ToString (),
597                                                 name.ToString ());
598                                 }
599                         }
600                         return accountName;
601                 }
602
603                 private static string FetchMessage (string msgDll, uint messageID, string [] replacementStrings)
604                 {
605                         // http://msdn.microsoft.com/library/en-us/debug/base/formatmessage.asp
606                         // http://msdn.microsoft.com/msdnmag/issues/02/08/CQA/
607                         // http://msdn.microsoft.com/netframework/programming/netcf/cffaq/default.aspx
608
609                         IntPtr msgDllHandle = PInvoke.LoadLibraryEx (msgDll, IntPtr.Zero,
610                                 LoadFlags.LibraryAsDataFile);
611                         if (msgDllHandle == IntPtr.Zero)
612                                 // TODO: write warning
613                                 return null;
614
615                         IntPtr lpMsgBuf = IntPtr.Zero;
616                         IntPtr [] arguments = new IntPtr [replacementStrings.Length];
617
618                         try {
619                                 for (int i = 0; i < replacementStrings.Length; i++) {
620                                         arguments [i] = Marshal.StringToHGlobalAuto (
621                                                 replacementStrings [i]);
622                                 }
623
624                                 int ret = PInvoke.FormatMessage (FormatMessageFlags.ArgumentArray |
625                                         FormatMessageFlags.FromHModule | FormatMessageFlags.AllocateBuffer,
626                                         msgDllHandle, messageID, 0, ref lpMsgBuf, 0, arguments);
627                                 if (ret != 0) {
628                                         string sRet = Marshal.PtrToStringAuto (lpMsgBuf);
629                                         lpMsgBuf = PInvoke.LocalFree (lpMsgBuf);
630                                         // remove trailing whitespace (CRLF)
631                                         return sRet.TrimEnd (null);
632                                 } else {
633                                         int err = Marshal.GetLastWin32Error ();
634                                         if (err == MESSAGE_NOT_FOUND) {
635                                                 // do not consider this a failure (or even warning) as
636                                                 // multiple message resource DLLs may have been configured
637                                                 // and as such we just need to try the next library if
638                                                 // the current one does not contain a message for this
639                                                 // ID
640                                         } else {
641                                                 // TODO: report warning
642                                         }
643                                 }
644                         } finally {
645                                 // release unmanaged memory allocated for replacement strings
646                                 for (int i = 0; i < arguments.Length; i++) {
647                                         IntPtr argument = arguments [i];
648                                         if (argument != IntPtr.Zero)
649                                                 Marshal.FreeHGlobal (argument);
650                                 }
651
652                                 PInvoke.FreeLibrary (msgDllHandle);
653                         }
654                         return null;
655                 }
656
657                 private string [] GetMessageResourceDlls (string source, string valueName)
658                 {
659                         // Some event sources (such as Userenv) have multiple message
660                         // resource DLLs, delimited by a semicolon.
661
662                         RegistryKey sourceKey = FindSourceKeyByName (source,
663                                 CoreEventLog.MachineName, false);
664                         if (sourceKey != null) {
665                                 string value = sourceKey.GetValue (valueName) as string;
666                                 if (value != null) {
667                                         string [] msgResDlls = value.Split (';');
668                                         return msgResDlls;
669                                 }
670                         }
671                         return new string [0];
672                 }
673
674                 private IntPtr ReadHandle {
675                         get {
676                                 if (_readHandle != IntPtr.Zero)
677                                         return _readHandle;
678
679                                 string logName = CoreEventLog.GetLogName ();
680                                 _readHandle = PInvoke.OpenEventLog (CoreEventLog.MachineName,
681                                         logName);
682                                 if (_readHandle == IntPtr.Zero)
683                                         throw new InvalidOperationException (string.Format (
684                                                 CultureInfo.InvariantCulture, "Event Log '{0}' on computer"
685                                                 + " '{1}' cannot be opened.", logName, CoreEventLog.MachineName),
686                                                 new Win32Exception ());
687                                 return _readHandle;
688                         }
689                 }
690
691                 private IntPtr RegisterEventSource ()
692                 {
693                         IntPtr hEventLog = PInvoke.RegisterEventSource (
694                                 CoreEventLog.MachineName, CoreEventLog.Source);
695                         if (hEventLog == IntPtr.Zero) {
696                                 throw new InvalidOperationException (string.Format (
697                                         CultureInfo.InvariantCulture, "Event source '{0}' on computer"
698                                         + " '{1}' cannot be opened.", CoreEventLog.Source,
699                                         CoreEventLog.MachineName), new Win32Exception ());
700                         }
701                         return hEventLog;
702                 }
703
704                 public override void DisableNotification ()
705                 {
706                         if (_notifyResetEvent != null) {
707                                 _notifyResetEvent.Close ();
708                                 _notifyResetEvent = null;
709                         }
710
711                         if (_notifyThread != null) {
712                                 if (_notifyThread.ThreadState == System.Threading.ThreadState.Running)
713                                         _notifyThread.Abort ();
714                                 _notifyThread = null;
715                         }
716                 }
717
718                 public override void EnableNotification ()
719                 {
720                         _notifyResetEvent = new ManualResetEvent (false);
721                         _lastEntryWritten = OldestEventLogEntry + EntryCount;
722                         if (PInvoke.NotifyChangeEventLog (ReadHandle, _notifyResetEvent.Handle) == 0)
723                                 throw new InvalidOperationException (string.Format (
724                                         CultureInfo.InvariantCulture, "Unable to receive notifications"
725                                         + " for log '{0}' on computer '{1}'.", CoreEventLog.GetLogName (),
726                                         CoreEventLog.MachineName), new Win32Exception ());
727                         _notifyThread = new Thread (new ThreadStart (NotifyEventThread));
728                         _notifyThread.IsBackground = true;
729                         _notifyThread.Start ();
730                 }
731
732                 private void NotifyEventThread ()
733                 {
734                         while (true) {
735                                 _notifyResetEvent.WaitOne ();
736                                 lock (this) {
737                                         // after a clear, we something get notified
738                                         // twice for the same entry
739                                         if (_notifying)
740                                                 return;
741                                         _notifying = true;
742                                 }
743
744                                 try {
745                                         int oldest_entry = OldestEventLogEntry;
746                                         if (_lastEntryWritten < oldest_entry)
747                                                 _lastEntryWritten = oldest_entry;
748                                         int current_entry = _lastEntryWritten - oldest_entry;
749                                         int last_entry = EntryCount + oldest_entry;
750                                         for (int i = current_entry; i < (last_entry - 1); i++) {
751                                                 EventLogEntry entry = GetEntry (i);
752                                                 CoreEventLog.OnEntryWritten (entry);
753                                         }
754                                         _lastEntryWritten = last_entry;
755                                 } finally {
756                                         lock (this)
757                                                 _notifying = false;
758                                 }
759                         }
760                 }
761
762                 public override OverflowAction OverflowAction {
763                         get { throw new NotImplementedException (); }
764                 }
765
766                 public override int MinimumRetentionDays {
767                         get { throw new NotImplementedException (); }
768                 }
769
770                 public override long MaximumKilobytes {
771                         get { throw new NotImplementedException (); }
772                         set { throw new NotImplementedException (); }
773                 }
774
775                 public override void ModifyOverflowPolicy (OverflowAction action, int retentionDays)
776                 {
777                         throw new NotImplementedException ();
778                 }
779
780                 public override void RegisterDisplayName (string resourceFile, long resourceId)
781                 {
782                         throw new NotImplementedException ();
783                 }
784
785                 private class PInvoke
786                 {
787                         [DllImport ("advapi32.dll", SetLastError=true)]
788                         public static extern int ClearEventLog (IntPtr hEventLog, string lpBackupFileName);
789
790                         [DllImport ("advapi32.dll", SetLastError=true)]
791                         public static extern int CloseEventLog (IntPtr hEventLog);
792
793                         [DllImport ("advapi32.dll", SetLastError=true)]
794                         public static extern int DeregisterEventSource (IntPtr hEventLog);
795
796                         [DllImport ("kernel32", CharSet=CharSet.Auto, SetLastError=true)]
797                         public static extern int FormatMessage (FormatMessageFlags dwFlags, IntPtr lpSource, uint dwMessageId, int dwLanguageId, ref IntPtr lpBuffer, int nSize, IntPtr [] arguments);
798
799                         [DllImport ("kernel32", SetLastError=true)]
800                         public static extern bool FreeLibrary (IntPtr hModule);
801
802                         [DllImport ("advapi32.dll", SetLastError=true)]
803                         public static extern int GetNumberOfEventLogRecords (IntPtr hEventLog, ref int NumberOfRecords);
804
805                         [DllImport ("advapi32.dll", SetLastError=true)]
806                         public static extern int GetOldestEventLogRecord (IntPtr hEventLog, ref int OldestRecord);
807
808                         [DllImport ("kernel32", SetLastError=true)]
809                         public static extern IntPtr LoadLibraryEx (string lpFileName, IntPtr hFile, LoadFlags dwFlags);
810
811                         [DllImport ("kernel32", SetLastError=true)]
812                         public static extern IntPtr LocalFree (IntPtr hMem);
813
814                         [DllImport ("advapi32.dll", SetLastError=true)]
815                         public static extern bool LookupAccountSid (
816                                 string lpSystemName,
817                                 [MarshalAs (UnmanagedType.LPArray)] byte [] Sid,
818                                 StringBuilder lpName,
819                                 ref uint cchName,
820                                 StringBuilder ReferencedDomainName,
821                                 ref uint cchReferencedDomainName,
822                                 out SidNameUse peUse);
823
824                         [DllImport ("advapi32.dll", SetLastError = true)]
825                         public static extern int NotifyChangeEventLog (IntPtr hEventLog, IntPtr hEvent);
826
827                         [DllImport ("advapi32.dll", SetLastError=true)]
828                         public static extern IntPtr OpenEventLog (string machineName, string logName);
829
830                         [DllImport ("advapi32.dll", SetLastError=true)]
831                         public static extern IntPtr RegisterEventSource (string machineName, string sourceName);
832
833                         [DllImport ("advapi32.dll", SetLastError=true)]
834                         public static extern int ReportEvent (IntPtr hHandle, ushort wType,
835                                 ushort wCategory, uint dwEventID, IntPtr sid, ushort wNumStrings,
836                                 uint dwDataSize, string [] lpStrings, byte [] lpRawData);
837
838                         [DllImport ("advapi32.dll", SetLastError=true)]
839                         public static extern int ReadEventLog (IntPtr hEventLog, ReadFlags dwReadFlags, int dwRecordOffset, byte [] buffer, int nNumberOfBytesToRead, ref int pnBytesRead, ref int pnMinNumberOfBytesNeeded);
840
841                         public const int ERROR_INSUFFICIENT_BUFFER = 122;
842                         public const int ERROR_EVENTLOG_FILE_CHANGED = 1503;
843                 }
844
845                 private enum ReadFlags
846                 {
847                         Sequential = 0x001,
848                         Seek = 0x002,
849                         ForwardsRead = 0x004,
850                         BackwardsRead = 0x008
851                 }
852
853                 private enum LoadFlags: uint
854                 {
855                         LibraryAsDataFile = 0x002
856                 }
857
858                 [Flags]
859                 private enum FormatMessageFlags
860                 {
861                         AllocateBuffer = 0x100,
862                         IgnoreInserts = 0x200,
863                         FromHModule = 0x0800,
864                         FromSystem = 0x1000,
865                         ArgumentArray = 0x2000
866                 }
867
868                 private enum SidNameUse
869                 {
870                         User = 1,
871                         Group,
872                         Domain,
873                         lias,
874                         WellKnownGroup,
875                         DeletedAccount,
876                         Invalid,
877                         Unknown,
878                         Computer
879                 }
880         }
881 }
882
883 // http://msdn.microsoft.com/library/en-us/eventlog/base/eventlogrecord_str.asp:
884 //
885 // struct EVENTLOGRECORD {
886 //      int Length;
887 //      int Reserved;
888 //      int RecordNumber;
889 //      int TimeGenerated;
890 //      int TimeWritten;
891 //      int EventID;
892 //      short EventType;
893 //      short NumStrings;
894 //      short EventCategory;
895 //      short ReservedFlags;
896 //      int ClosingRecordNumber;
897 //      int StringOffset;
898 //      int UserSidLength;
899 //      int UserSidOffset;
900 //      int DataLength;
901 //      int DataOffset;
902 // }
903 //
904 // http://www.whitehats.ca/main/members/Malik/malik_eventlogs/malik_eventlogs.html