Initial commit
[mono.git] / mcs / class / referencesource / System / services / monitoring / system / diagnosticts / EventLogInternal.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="EventLogInternal.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //------------------------------------------------------------------------------
6
7 //#define RETRY_ON_ALL_ERRORS
8
9 /*
10  * EventLogInternal contains most of the logic for interacting with the Windows Event Log.
11  * The reason for this class existing (instead of the logic being in EventLog itself) is 
12  * that we'd like to be able to have the invariant that the Source, MachineName and Log Name
13  * don't change across the lifetime of an event log object, but we exposed public setters for
14  * these properties.  EventLog holds a reference to an EventLogInternal instance, plumbs all
15  * calls to it and replaces it when any of these properites change.
16  * 
17  * Note that EventLogInternal also holds a reference back to the EventLog instnace that is 
18  * exposing it so it is not prematurely collected.
19  */
20
21 namespace System.Diagnostics {
22     using System.Text;
23     using System.Text.RegularExpressions;
24     using System.Threading;
25     using System.Runtime.InteropServices;
26     using System.ComponentModel;
27     using System.Diagnostics;
28     using System;
29     using Microsoft.Win32;
30     using Microsoft.Win32.SafeHandles;
31     using System.IO;
32     using System.Collections;
33     using System.Collections.Specialized;
34     using System.Globalization;
35     using System.ComponentModel.Design;
36     using System.Security;
37     using System.Security.Permissions;
38     using System.Reflection;
39     using System.Runtime.Versioning;
40     using System.Runtime.CompilerServices;
41     using System.Diagnostics.CodeAnalysis;
42
43     /// <devdoc>
44     ///    <para>
45     ///       Provides interaction with Windows 2000 event logs.
46     ///    </para>
47     /// </devdoc>
48     internal class EventLogInternal : IDisposable, ISupportInitialize {
49         // a collection over all our entries. Since the class holds no state, we
50         // can just hand the same instance out every time.
51         private EventLogEntryCollection entriesCollection;
52         // the name of the log we're reading from or writing to
53         internal string logName;
54         // used in monitoring for event postings.
55         private int lastSeenCount;
56         // holds the machine we're on, or null if it's the local machine
57         internal readonly string machineName;
58
59         // the delegate to call when an event arrives
60         internal EntryWrittenEventHandler onEntryWrittenHandler;
61         // holds onto the handle for reading
62         private SafeEventLogReadHandle  readHandle;
63         // the source name - used only when writing
64         internal readonly string sourceName;
65         // holds onto the handle for writing
66         private SafeEventLogWriteHandle writeHandle;
67
68         private string logDisplayName;
69
70         // cache system state variables
71         // the initial size of the buffer (it can be made larger if necessary)
72         private const int BUF_SIZE = 40000;
73         // the number of bytes in the cache that belong to entries (not necessarily
74         // the same as BUF_SIZE, because the cache only holds whole entries)
75         private int bytesCached;
76         // the actual cache buffer
77         private byte[] cache;
78         // the number of the entry at the beginning of the cache
79         private int firstCachedEntry = -1;
80         // the number of the entry that we got out of the cache most recently
81         private int lastSeenEntry;
82         // where that entry was
83         private int lastSeenPos;
84         //support for threadpool based deferred execution
85         private ISynchronizeInvoke synchronizingObject;
86         // the EventLog object that publicly exposes this instance.
87         private readonly EventLog parent;
88
89         private const string EventLogKey = "SYSTEM\\CurrentControlSet\\Services\\EventLog";
90         internal const string DllName = "EventLogMessages.dll";
91         private const string eventLogMutexName = "netfxeventlog.1.0";
92         private const int SecondsPerDay = 60 * 60 * 24;
93         private const int DefaultMaxSize = 512*1024;
94         private const int DefaultRetention = 7*SecondsPerDay;
95         
96         private const int Flag_notifying     = 0x1;           // keeps track of whether we're notifying our listeners - to prevent double notifications
97         private const int Flag_forwards      = 0x2;     // whether the cache contains entries in forwards order (true) or backwards (false)
98         private const int Flag_initializing  = 0x4;
99         internal const int Flag_monitoring    = 0x8;
100         private const int Flag_registeredAsListener  = 0x10;
101         private const int Flag_writeGranted     = 0x20;
102         private const int Flag_disposed      = 0x100;
103         private const int Flag_sourceVerified= 0x200;
104         
105         private BitVector32 boolFlags = new BitVector32();
106         
107         private Hashtable messageLibraries;
108         private readonly static Hashtable listenerInfos = new Hashtable(StringComparer.OrdinalIgnoreCase);
109
110         private Object m_InstanceLockObject;
111         private Object InstanceLockObject {
112             get {
113                 if (m_InstanceLockObject == null) {
114                     Object o = new Object();
115                     Interlocked.CompareExchange(ref m_InstanceLockObject, o, null);
116                 }
117                 return m_InstanceLockObject;
118             }
119         }
120
121
122         private static Object s_InternalSyncObject;
123         private static Object InternalSyncObject {
124             get {
125                 if (s_InternalSyncObject == null) {
126                     Object o = new Object();
127                     Interlocked.CompareExchange(ref s_InternalSyncObject, o, null);
128                 }
129                 return s_InternalSyncObject;
130             }
131         }
132
133         /// <devdoc>
134         ///    <para>
135         ///       Initializes a new instance of the <see cref='System.Diagnostics.EventLog'/>
136         ///       class.
137         ///    </para>
138         /// </devdoc>
139         public EventLogInternal() : this("", ".", "", null) {
140         }
141
142         /// <devdoc>
143         ///    <para>[To be supplied.]</para>
144         /// </devdoc>
145         public EventLogInternal(string logName) : this(logName, ".", "", null) {
146         }
147
148         /// <devdoc>
149         ///    <para>[To be supplied.]</para>
150         /// </devdoc>
151         public EventLogInternal(string logName, string machineName) : this(logName, machineName, "", null) {
152         }
153
154         /// <devdoc>
155         ///    <para>[To be supplied.]</para>
156         /// </devdoc>
157         public EventLogInternal(string logName, string machineName, string source) : this(logName, machineName, source, null) {
158         }
159
160         /// <devdoc>
161         ///    <para>[To be supplied.]</para>
162         /// </devdoc>
163         [SuppressMessage("Microsoft.Security", "CA2103:ReviewImperativeSecurity", Justification = "[....]: Safe, oldLog.machineName doesn't change")]
164         public EventLogInternal(string logName, string machineName, string source, EventLog parent) {
165             //look out for invalid log names
166             if (logName == null)
167                 throw new ArgumentNullException("logName");
168             if (!ValidLogName(logName, true))
169                 throw new ArgumentException(SR.GetString(SR.BadLogName));
170
171             if (!SyntaxCheck.CheckMachineName(machineName))
172                 throw new ArgumentException(SR.GetString(SR.InvalidParameter, "machineName", machineName));
173
174             EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Write, machineName);
175             permission.Demand();
176             
177             this.machineName = machineName;
178
179             this.logName = logName;
180             this.sourceName = source;
181             readHandle = null;
182             writeHandle = null;
183             boolFlags[Flag_forwards] = true;
184             this.parent = parent;
185         }
186
187         /// <devdoc>
188         ///    <para>
189         ///       Gets the contents of the event log.
190         ///    </para>
191         /// </devdoc>
192         public EventLogEntryCollection Entries {
193             get {
194                 string currentMachineName = this.machineName;
195
196                 EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Administer, currentMachineName);
197                 permission.Demand();
198
199                 if (entriesCollection == null)
200                     entriesCollection = new EventLogEntryCollection(this);
201                 return entriesCollection;
202             }
203         }
204
205         /// <devdoc>
206         ///     Gets the number of entries in the log
207         /// </devdoc>
208         internal int EntryCount {
209             get {
210                 if (!IsOpenForRead)
211                     OpenForRead(this.machineName);
212                 int count;
213                 bool success = UnsafeNativeMethods.GetNumberOfEventLogRecords(readHandle, out count);
214                 if (!success)
215                     throw SharedUtils.CreateSafeWin32Exception();
216                 return count;
217             }
218         }
219
220         /// <devdoc>
221         ///     Determines whether the event log is open in either read or write access
222         /// </devdoc>
223         private bool IsOpen {
224             get {
225                 return readHandle != null || writeHandle != null;
226             }
227         }
228
229         /// <devdoc>
230         ///     Determines whether the event log is open with read access
231         /// </devdoc>
232         private bool IsOpenForRead {
233             get {
234                 return readHandle != null;
235             }
236         }
237
238         /// <devdoc>
239         ///     Determines whether the event log is open with write access.
240         /// </devdoc>
241         private bool IsOpenForWrite {
242             get {
243                 return writeHandle != null;
244             }
245         }
246
247         /// <devdoc>
248         ///    <para>
249         ///    </para>
250         /// </devdoc>
251         public string LogDisplayName {
252         [ResourceExposure(ResourceScope.Machine)]
253         [ResourceConsumption(ResourceScope.Machine)]
254             get {
255
256                 if (logDisplayName != null)
257                     return logDisplayName;
258
259                 string currentMachineName = this.machineName;
260                 if (GetLogName(currentMachineName) != null) {
261                         
262                     EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Administer, currentMachineName);
263                     permission.Demand();
264
265                     //Check environment before looking at the registry
266                     SharedUtils.CheckEnvironment();
267
268                     //SECREVIEW: Note that EventLogPermission is just demmanded above
269                     PermissionSet permissionSet = EventLog._UnsafeGetAssertPermSet();
270                     permissionSet.Assert();
271
272                     RegistryKey logkey = null;
273
274                     try {
275                         // we figure out what logs are on the machine by looking in the registry.
276                         logkey = GetLogRegKey(currentMachineName, false);
277                         if (logkey == null)
278                             throw new InvalidOperationException(SR.GetString(SR.MissingLog, GetLogName(currentMachineName), currentMachineName));
279
280                         string resourceDll = (string)logkey.GetValue("DisplayNameFile");
281                         if (resourceDll == null)
282                             logDisplayName = GetLogName(currentMachineName);
283                         else {
284                             int resourceId = (int)logkey.GetValue("DisplayNameID");
285                             logDisplayName = FormatMessageWrapper(resourceDll, (uint) resourceId, null);
286                             if (logDisplayName == null)
287                                 logDisplayName = GetLogName(currentMachineName);
288                         }
289                     }
290                     finally {
291                         if (logkey != null) logkey.Close();
292
293                         // Revert registry and environment permission asserts
294                         CodeAccessPermission.RevertAssert();
295                     }
296                 }
297
298                 return logDisplayName;
299             }
300         }
301
302         /// <devdoc>
303         ///    <para>
304         ///       Gets the name of the log to read from and write to.
305         ///    </para>
306         /// </devdoc>
307         public string Log {
308             get {
309                 string currentMachineName = this.machineName;
310                 if (logName == null || logName.Length == 0) {
311                     EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Administer, currentMachineName);
312                     permission.Demand();
313                 }
314
315                 return GetLogName(currentMachineName);
316             }
317         }
318
319         [SuppressMessage("Microsoft.Security", "CA2103:ReviewImperativeSecurity", Justification = "[....]: Safe, machineName doesn't change")]
320         private string GetLogName(string currentMachineName)
321         {
322             if ((logName == null || logName.Length == 0) && sourceName != null && sourceName.Length!=0) {
323                 // they've told us a source, but they haven't told us a log name.
324                 // try to deduce the log name from the source name.
325                 EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Write, currentMachineName);
326                 permission.Demand();
327
328                 logName = EventLog._InternalLogNameFromSourceName(sourceName, currentMachineName);
329             }
330             return logName;
331         }
332
333         /// <devdoc>
334         ///    <para>
335         ///       Gets the name of the computer on which to read or write events.
336         ///    </para>
337         /// </devdoc>
338         public string MachineName {
339             get {
340                 string currentMachineName = this.machineName;
341
342                 EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Write, currentMachineName);
343                 permission.Demand();
344
345                 return currentMachineName;
346             }
347         }
348
349         [ComVisible(false)]
350         [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "[....]: MaximumKilobytes is the name of this property.")]
351         public long MaximumKilobytes {
352             get {
353                 string currentMachineName = this.machineName;
354
355                 EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Administer, currentMachineName);
356                 permission.Demand();
357
358                 object val = GetLogRegValue(currentMachineName, "MaxSize");
359                 if (val != null) {
360                     int intval = (int) val;         // cast to an int first to unbox
361                     return ((uint)intval) / 1024;   // then convert to kilobytes
362                 }
363
364                 // 512k is the default value
365                 return 0x200;
366             }
367
368             [ResourceExposure(ResourceScope.None)]
369             [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
370             set {
371                 string currentMachineName = this.machineName;
372                 
373                 EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Administer, currentMachineName);
374                 permission.Demand();
375
376                 // valid range is 64 KB to 4 GB
377                 if (value < 64 || value > 0x3FFFC0 || value % 64 != 0)
378                     throw new ArgumentOutOfRangeException("MaximumKilobytes", SR.GetString(SR.MaximumKilobytesOutOfRange));
379
380                 PermissionSet permissionSet = EventLog._UnsafeGetAssertPermSet();
381                 permissionSet.Assert();
382
383                 long regvalue = value * 1024; // convert to bytes
384                 int i = unchecked((int)regvalue);
385
386                 using (RegistryKey logkey = GetLogRegKey(currentMachineName, true))
387                     logkey.SetValue("MaxSize", i, RegistryValueKind.DWord);
388             }
389         }
390
391         internal Hashtable MessageLibraries {
392             get {
393                 if (messageLibraries == null)
394                     messageLibraries = new Hashtable(StringComparer.OrdinalIgnoreCase);
395                 return messageLibraries;
396             }
397         }
398
399         [ComVisible(false)]
400         public OverflowAction OverflowAction {
401             get {
402                 string currentMachineName = this.machineName;
403
404                 EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Administer, currentMachineName);
405                 permission.Demand();
406
407                 object retentionobj  = GetLogRegValue(currentMachineName, "Retention");
408                 if (retentionobj  != null) {
409                     int retention = (int) retentionobj;
410                     if (retention == 0)
411                         return OverflowAction.OverwriteAsNeeded;
412                     else if (retention == -1)
413                         return OverflowAction.DoNotOverwrite;
414                     else
415                         return OverflowAction.OverwriteOlder;
416                 }
417
418                 // default value as listed in MSDN
419                 return OverflowAction.OverwriteOlder;
420             }
421         }
422
423         [ComVisible(false)]
424         public int MinimumRetentionDays {
425             get {
426                 string currentMachineName = this.machineName;
427
428                 EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Administer, currentMachineName);
429                 permission.Demand();
430
431                 object retentionobj  = GetLogRegValue(currentMachineName, "Retention");
432                 if (retentionobj  != null) {
433                     int retention = (int) retentionobj;
434                     if (retention == 0 || retention == -1)
435                         return retention;
436                     else
437                         return (int) (((double) retention) / SecondsPerDay);
438                 }
439                 return 7;
440             }
441         }
442
443         /// <devdoc>
444         /// </devdoc>
445         public bool EnableRaisingEvents {
446             get {
447                 string currentMachineName = this.machineName;
448
449                 EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Write, currentMachineName);
450                 permission.Demand();
451
452                 return boolFlags[Flag_monitoring];
453             }
454             set {
455                 string currentMachineName = this.machineName;
456
457                 EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Write, currentMachineName);
458                 permission.Demand();
459
460                 if (parent.ComponentDesignMode)
461                     this.boolFlags[Flag_monitoring] = value;
462                 else {
463                     if (value)
464                         StartRaisingEvents(currentMachineName, GetLogName(currentMachineName));
465                     else
466                         StopRaisingEvents(/*currentMachineName,*/ GetLogName(currentMachineName));
467                 }
468             }
469         }
470
471         private int OldestEntryNumber {
472             get {
473                 if (!IsOpenForRead)
474                     OpenForRead(this.machineName);
475                 int num;
476                 bool success = UnsafeNativeMethods.GetOldestEventLogRecord(readHandle, out num);
477                 if (!success)
478                     throw SharedUtils.CreateSafeWin32Exception();
479
480                 // When the event log is empty, GetOldestEventLogRecord returns 0.
481                 // But then after an entry is written, it returns 1. We need to go from
482                 // the last num to the current.
483                 if (num == 0)
484                     num = 1;
485
486                 return num;
487             }
488         }
489
490         internal SafeEventLogReadHandle  ReadHandle {
491             get {
492                 if (!IsOpenForRead)
493                     OpenForRead(this.machineName);
494                 return readHandle;
495             }
496         }
497
498         /// <devdoc>
499         ///    <para>
500         ///       Represents the object used to marshal the event handler
501         ///       calls issued as a result of an <see cref='System.Diagnostics.EventLog'/>
502         ///       change.
503         ///    </para>
504         /// </devdoc>
505         public ISynchronizeInvoke SynchronizingObject {
506         [HostProtection(Synchronization=true)]
507             get {
508                 string currentMachineName = this.machineName;
509
510                 EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Write, currentMachineName);
511                 permission.Demand();
512                 if (this.synchronizingObject == null && parent.ComponentDesignMode) {
513                     IDesignerHost host = (IDesignerHost) parent.ComponentGetService(typeof(IDesignerHost));
514                     if (host != null) {
515                         object baseComponent = host.RootComponent;
516                         if (baseComponent != null && baseComponent is ISynchronizeInvoke)
517                             this.synchronizingObject = (ISynchronizeInvoke)baseComponent;
518                     }
519                 }
520
521                 return this.synchronizingObject;
522             }
523
524             set {
525                 this.synchronizingObject = value;
526             }
527         }
528
529         /// <devdoc>
530         ///    <para>
531         ///       Gets or
532         ///       sets the application name (source name) to register and use when writing to the event log.
533         ///    </para>
534         /// </devdoc>
535         public string Source {
536             get {
537                 string currentMachineName = this.machineName;
538
539                 EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Write, currentMachineName);
540                 permission.Demand();
541                 return sourceName;
542             }
543         }
544
545
546
547         [HostProtection(Synchronization=true)]
548         [ResourceExposure(ResourceScope.None)]
549         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
550         private static void AddListenerComponent(EventLogInternal component, string compMachineName, string compLogName) {
551             lock (InternalSyncObject) {
552                 Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::AddListenerComponent(" + compLogName + ")");
553
554                 LogListeningInfo info = (LogListeningInfo) listenerInfos[compLogName];
555                 if (info != null) {
556                     Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::AddListenerComponent: listener already active.");
557                     info.listeningComponents.Add(component);
558                     return;
559                 }
560
561                 info = new LogListeningInfo();
562                 info.listeningComponents.Add(component);
563
564                 info.handleOwner = new EventLogInternal(compLogName, compMachineName);
565
566                 // tell the event log system about it
567                 info.waitHandle = new AutoResetEvent(false); 
568                 bool success = UnsafeNativeMethods.NotifyChangeEventLog(info.handleOwner.ReadHandle, info.waitHandle.SafeWaitHandle);
569                 if (!success)
570                     throw new InvalidOperationException(SR.GetString(SR.CantMonitorEventLog), SharedUtils.CreateSafeWin32Exception());
571
572                 info.registeredWaitHandle = ThreadPool.RegisterWaitForSingleObject(info.waitHandle, new WaitOrTimerCallback(StaticCompletionCallback), info, -1, false);
573
574                 listenerInfos[compLogName] = info;
575             }
576         }
577
578         /// <devdoc>
579         ///    <para>
580         ///       Occurs when an entry is written to the event log.
581         ///    </para>
582         /// </devdoc>
583         public event EntryWrittenEventHandler EntryWritten {
584             add {
585                 string currentMachineName = this.machineName;
586
587                 EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Administer, currentMachineName);
588                 permission.Demand();
589
590                 onEntryWrittenHandler += value;
591             }
592             remove {
593                 string currentMachineName = this.machineName;
594
595                 EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Administer, currentMachineName);
596                 permission.Demand();
597
598                 onEntryWrittenHandler -= value;
599             }
600         }
601
602         /// <devdoc>
603         /// </devdoc>
604         public void BeginInit() {
605             string currentMachineName = this.machineName;
606
607             EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Write, currentMachineName);
608             permission.Demand();
609
610             if (boolFlags[Flag_initializing]) throw new InvalidOperationException(SR.GetString(SR.InitTwice));
611             boolFlags[Flag_initializing] = true;
612             if (boolFlags[Flag_monitoring])
613                 StopListening(GetLogName(currentMachineName));
614         }
615
616         /// <devdoc>
617         ///    <para>
618         ///       Clears
619         ///       the event log by removing all entries from it.
620         ///    </para>
621         /// </devdoc>
622         [ResourceExposure(ResourceScope.Machine)]  // Should anyone ever call this, other than an event log viewer?
623         [ResourceConsumption(ResourceScope.Machine)]
624         public void Clear() {
625             string currentMachineName = this.machineName;
626
627             EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Administer, currentMachineName);
628             permission.Demand();
629
630             if (!IsOpenForRead)
631                 OpenForRead(currentMachineName);
632             bool success = UnsafeNativeMethods.ClearEventLog(readHandle, NativeMethods.NullHandleRef);
633             if (!success) {
634                 // Ignore file not found errors.  ClearEventLog seems to try to delete the file where the event log is
635                 // stored.  If it can't find it, it gives an error. 
636                 int error = Marshal.GetLastWin32Error();
637                 if (error != NativeMethods.ERROR_FILE_NOT_FOUND)
638                     throw SharedUtils.CreateSafeWin32Exception();
639             }
640             
641             // now that we've cleared the event log, we need to re-open our handles, because
642             // the internal state of the event log has changed.
643             Reset(currentMachineName);
644         }
645
646         /// <devdoc>
647         ///    <para>
648         ///       Closes the event log and releases read and write handles.
649         ///    </para>
650         /// </devdoc>
651         [ResourceExposure(ResourceScope.None)]
652         public void Close() {
653             Close(this.machineName);
654         }
655
656         [SuppressMessage("Microsoft.Security", "CA2103:ReviewImperativeSecurity", Justification = "[....]: Safe, currentMachineName doesn't change")]
657         private void Close(string currentMachineName) {
658             EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Write, currentMachineName);
659             permission.Demand();
660
661             Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::Close");
662             //Trace("Close", "Closing the event log");
663             if (readHandle != null) {
664                 try {
665                     readHandle.Close();
666                 }
667                 catch (IOException) {
668                     throw SharedUtils.CreateSafeWin32Exception();
669                 }
670                 readHandle = null;
671                 //Trace("Close", "Closed read handle");
672                 Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::Close: closed read handle");
673             }
674             if (writeHandle != null) {
675                 try {
676                     writeHandle.Close();
677                 }
678                 catch (IOException) {
679                     throw SharedUtils.CreateSafeWin32Exception();
680                 }
681                 writeHandle = null;
682                 //Trace("Close", "Closed write handle");
683                 Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::Close: closed write handle");
684             }
685             if (boolFlags[Flag_monitoring])
686                 StopRaisingEvents(/*currentMachineName,*/ GetLogName(currentMachineName));
687
688             if (messageLibraries != null) {
689                 foreach (SafeLibraryHandle handle in messageLibraries.Values)
690                     handle.Close();
691
692                 messageLibraries = null;
693             }
694             
695             boolFlags[Flag_sourceVerified] = false;
696         }
697
698
699         /// <internalonly/>
700         /// <devdoc>
701         ///     Called when the threadpool is ready for us to handle a status change.
702         /// </devdoc>
703         private void CompletionCallback(object context)  {
704
705             if (boolFlags[Flag_disposed]) {
706                 // This object has been disposed previously, ignore firing the event.
707                 return;
708             }
709
710             Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::CompletionStatusChanged: starting at " + lastSeenCount.ToString(CultureInfo.InvariantCulture));
711             lock (InstanceLockObject) {
712                 if (boolFlags[Flag_notifying]) {
713                     // don't do double notifications.
714                     Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::CompletionStatusChanged: aborting because we're already notifying.");
715                     return;
716                 }
717                 boolFlags[Flag_notifying] = true;
718             }
719
720             int i = lastSeenCount;
721
722             try {
723                 int oldest = OldestEntryNumber;
724                 int count = EntryCount + oldest;
725
726                 // Ensure lastSeenCount is within bounds.  This deals with the case where the event log has been cleared between
727                 // notifications.
728                 if (lastSeenCount < oldest || lastSeenCount > count) {
729                     lastSeenCount = oldest;
730                     i = lastSeenCount;
731                 }
732
733                 // NOTE, [....]: We have a double loop here so that we access the
734                 // EntryCount property as infrequently as possible. (It may be expensive
735                 // to get the property.) Even though there are two loops, they will together
736                 // only execute as many times as (final value of EntryCount) - lastSeenCount.
737                 Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::CompletionStatusChanged: OldestEntryNumber is " + OldestEntryNumber + ", EntryCount is " + EntryCount);
738                 while (i < count) {
739                     while (i < count) {
740                         EventLogEntry entry = GetEntryWithOldest(i);
741                         if (this.SynchronizingObject != null && this.SynchronizingObject.InvokeRequired)
742                             this.SynchronizingObject.BeginInvoke(this.onEntryWrittenHandler, new object[]{this, new EntryWrittenEventArgs(entry)});
743                         else
744                            onEntryWrittenHandler(this, new EntryWrittenEventArgs(entry));
745
746                         i++;
747                     }
748                     oldest = OldestEntryNumber;
749                     count = EntryCount + oldest;
750                 }
751             }
752             catch (Exception e) {
753                 Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::CompletionStatusChanged: Caught exception notifying event handlers: " + e.ToString());
754             }
755
756             try {
757                 // if the user cleared the log while we were receiving events, the call to GetEntryWithOldest above could have 
758                 // thrown an exception and i could be too large.  Make sure we don't set lastSeenCount to something bogus.
759                 int newCount = EntryCount + OldestEntryNumber;
760                 if (i > newCount)
761                     lastSeenCount = newCount;
762                 else
763                     lastSeenCount = i;
764                 Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::CompletionStatusChanged: finishing at " + lastSeenCount.ToString(CultureInfo.InvariantCulture));
765             }
766             catch (Win32Exception e) {
767                 Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::CompletionStatusChanged: Caught exception updating last entry number: " + e.ToString());
768             }
769
770             lock (InstanceLockObject) {
771                 boolFlags[Flag_notifying] = false;
772             }
773        }
774
775         public void Dispose() {
776             Dispose(true);
777             GC.SuppressFinalize(this);
778         }
779
780         /// <devdoc>
781         /// </devdoc>
782         internal void Dispose(bool disposing) {
783             try {
784                 if (disposing) {
785                     //Dispose unmanaged and managed resources
786                     if (IsOpen) {
787                         Close();
788                     }
789
790                     // This is probably unnecessary
791                     if (readHandle != null) {
792                     readHandle.Close();
793                         readHandle = null;
794                     }
795
796                     if (writeHandle != null) {
797                     writeHandle.Close();
798                         writeHandle = null;
799                     }
800                 }
801             }
802             finally {
803                 messageLibraries = null;
804                 this.boolFlags[Flag_disposed] = true;
805             }
806         }
807
808         /// <devdoc>
809         /// </devdoc>
810         public void EndInit() {
811             string currentMachineName = this.machineName;
812
813             EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Write, currentMachineName);
814             permission.Demand();
815
816             boolFlags[Flag_initializing] = false;
817             if (boolFlags[Flag_monitoring])
818                 StartListening(currentMachineName, GetLogName(currentMachineName));
819         }
820
821         [ResourceExposure(ResourceScope.None)]
822         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
823         internal string FormatMessageWrapper(string dllNameList, uint messageNum, string[] insertionStrings) {
824             if (dllNameList == null)
825                 return null;
826
827             if (insertionStrings == null)
828                 insertionStrings = new string[0];
829
830             string[] listDll = dllNameList.Split(';');
831
832             // Find first mesage in DLL list
833             foreach ( string dllName in  listDll) {
834                 if (dllName == null || dllName.Length == 0)
835                     continue;
836
837                 SafeLibraryHandle hModule = null;
838
839                 // if the EventLog is open, then we want to cache the library in our hashtable.  Otherwise
840                 // we'll just load it and free it after we're done.
841                 if (IsOpen) {
842                     hModule = MessageLibraries[dllName] as SafeLibraryHandle;
843
844                     if (hModule == null || hModule.IsInvalid) {
845                         hModule = SafeLibraryHandle.LoadLibraryEx(dllName, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE);
846                         MessageLibraries[dllName] = hModule;
847                     }
848                 }
849                 else {
850                     hModule = SafeLibraryHandle.LoadLibraryEx(dllName, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE);
851                 }
852
853                 if (hModule.IsInvalid)
854                     continue;
855
856                 string msg = null;
857                 try {
858                     msg = EventLog.TryFormatMessage(hModule, messageNum, insertionStrings);
859                 }
860                 finally {
861                     if (!IsOpen) {
862                         hModule.Close();
863                     }
864                 }
865
866                 if ( msg != null ) {
867                     return msg;
868                 }
869
870             }
871             return null;
872         }
873
874         /// <devdoc>
875         ///     Gets an array of EventLogEntry's, one for each entry in the log.
876         /// </devdoc>
877         [ResourceExposure(ResourceScope.None)]
878         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
879         internal EventLogEntry[] GetAllEntries() {
880             // we could just call getEntryAt() on all the entries, but it'll be faster
881             // if we grab multiple entries at once.
882             string currentMachineName = this.machineName;
883
884             if (!IsOpenForRead)
885                 OpenForRead(currentMachineName);
886
887             EventLogEntry[] entries = new EventLogEntry[EntryCount];
888             int idx = 0;
889             int oldestEntry = OldestEntryNumber;
890
891             int bytesRead;
892             int minBytesNeeded;
893             int error = 0;
894             while (idx < entries.Length) {
895                 byte[] buf = new byte[BUF_SIZE];
896                 bool success = UnsafeNativeMethods.ReadEventLog(readHandle, NativeMethods.FORWARDS_READ | NativeMethods.SEEK_READ,
897                                                       oldestEntry+idx, buf, buf.Length, out bytesRead, out minBytesNeeded);
898                 if (!success) {
899                     error = Marshal.GetLastWin32Error();
900                     // NOTE, [....]: ERROR_PROC_NOT_FOUND used to get returned, but I think that
901                     // was because I was calling GetLastError directly instead of GetLastWin32Error.
902                     // Making the buffer bigger and trying again seemed to work. I've removed the check
903                     // for ERROR_PROC_NOT_FOUND because I don't think it's necessary any more, but
904                     // I can't prove it...
905                     Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "Error from ReadEventLog is " + error.ToString(CultureInfo.InvariantCulture));
906 #if !RETRY_ON_ALL_ERRORS
907                     if (error == NativeMethods.ERROR_INSUFFICIENT_BUFFER || error == NativeMethods.ERROR_EVENTLOG_FILE_CHANGED) {
908 #endif
909                         if (error == NativeMethods.ERROR_EVENTLOG_FILE_CHANGED) {
910                             // somewhere along the way the event log file changed - probably it
911                             // got cleared while we were looping here. Reset the handle and
912                             // try again.
913                             Reset(currentMachineName);
914                         }
915                         // try again with a bigger buffer if necessary
916                         else if (minBytesNeeded > buf.Length) {
917                             Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "Increasing buffer size from " + buf.Length.ToString(CultureInfo.InvariantCulture) + " to " + minBytesNeeded.ToString(CultureInfo.InvariantCulture) + " bytes");
918                             buf = new byte[minBytesNeeded];
919                         }
920                         success = UnsafeNativeMethods.ReadEventLog(readHandle, NativeMethods.FORWARDS_READ | NativeMethods.SEEK_READ,
921                                                          oldestEntry+idx, buf, buf.Length, out bytesRead, out minBytesNeeded);
922                         if (!success)
923                             // we'll just stop right here.
924                             break;
925 #if !RETRY_ON_ALL_ERRORS
926                     }
927                     else {
928                         break;
929                     }
930 #endif
931                     error = 0;
932                 }
933                 entries[idx] = new EventLogEntry(buf, 0, this);
934                 int sum = IntFrom(buf, 0);
935                 idx++;
936                 while (sum < bytesRead && idx < entries.Length) {
937                     entries[idx] = new EventLogEntry(buf, sum, this);
938                     sum += IntFrom(buf, sum);
939                     idx++;
940                 }
941             }
942             if (idx != entries.Length) {
943                 if (error != 0)
944                     throw new InvalidOperationException(SR.GetString(SR.CantRetrieveEntries), SharedUtils.CreateSafeWin32Exception(error));
945                 else
946                     throw new InvalidOperationException(SR.GetString(SR.CantRetrieveEntries));
947             }
948             return entries;
949         }
950
951         /// <devdoc>
952         ///     Searches the cache for an entry with the given index
953         /// </devdoc>
954         private int GetCachedEntryPos(int entryIndex) {
955             if (cache == null || (boolFlags[Flag_forwards] && entryIndex < firstCachedEntry) ||
956                 (!boolFlags[Flag_forwards] && entryIndex > firstCachedEntry) || firstCachedEntry == -1) {
957                 // the index falls before anything we have in the cache, or the cache
958                 // is not yet valid
959                 return -1;
960             }
961             // we only know where the beginning of the cache is, not the end, so even
962             // if it's past the end of the cache, we'll have to search through the whole
963             // cache to find out.
964
965             // we're betting heavily that the one they want to see now is close
966             // to the one they asked for last time. We start looking where we
967             // stopped last time.
968
969             // We have two loops, one to go forwards and one to go backwards. Only one
970             // of them will ever be executed.
971             while (lastSeenEntry < entryIndex) {
972                 lastSeenEntry++;
973                 if (boolFlags[Flag_forwards]) {
974                     lastSeenPos = GetNextEntryPos(lastSeenPos);
975                     if (lastSeenPos >= bytesCached)
976                         break;
977                 }
978                 else {
979                     lastSeenPos = GetPreviousEntryPos(lastSeenPos);
980                     if (lastSeenPos < 0)
981                         break;
982                 }
983             }
984             while (lastSeenEntry > entryIndex) {
985                 lastSeenEntry--;
986                 if (boolFlags[Flag_forwards]) {
987                     lastSeenPos = GetPreviousEntryPos(lastSeenPos);
988                     if (lastSeenPos < 0)
989                         break;
990                 }
991                 else {
992                     lastSeenPos = GetNextEntryPos(lastSeenPos);
993                     if (lastSeenPos >= bytesCached)
994                         break;
995                 }
996             }
997             if (lastSeenPos >= bytesCached) {
998                 // we ran past the end. move back to the last one and return -1
999                 lastSeenPos = GetPreviousEntryPos(lastSeenPos);
1000                 if (boolFlags[Flag_forwards])
1001                     lastSeenEntry--;
1002                 else
1003                     lastSeenEntry++;
1004                 return -1;
1005             }
1006             else if (lastSeenPos < 0) {
1007                 // we ran past the beginning. move back to the first one and return -1
1008                 lastSeenPos = 0;
1009                 if (boolFlags[Flag_forwards])
1010                     lastSeenEntry++;
1011                 else
1012                     lastSeenEntry--;
1013                 return -1;
1014             }
1015             else {
1016                 // we found it.
1017                 return lastSeenPos;
1018             }
1019         }
1020
1021         /// <devdoc>
1022         ///     Gets the entry at the given index
1023         /// </devdoc>
1024         internal EventLogEntry GetEntryAt(int index) {
1025             EventLogEntry entry = GetEntryAtNoThrow(index);
1026             if (entry == null)
1027                 throw new ArgumentException(SR.GetString(SR.IndexOutOfBounds, index.ToString(CultureInfo.CurrentCulture)));
1028             return entry;
1029         }
1030
1031         internal EventLogEntry GetEntryAtNoThrow(int index) {
1032             if (!IsOpenForRead)
1033                 OpenForRead(this.machineName);
1034
1035             if (index < 0 || index >= EntryCount)
1036                 return null;
1037
1038             // 
1039
1040             index += OldestEntryNumber;
1041             EventLogEntry entry = null;
1042
1043             try {
1044                 entry = GetEntryWithOldest(index);
1045             }
1046             catch (InvalidOperationException) {
1047                 // This would be common in rapidly spinning EventLog (i.e. logs which are rapidly receiving 
1048                 // new events while discarding old ones in a rolling fashion) or if the EventLog is cleared asynchronously.
1049                 //
1050                 // EventLogEntryCollection heuristics is little bit convoluted due to the inherent ----s. 
1051                 // The enumerator predominantly operates on the index from the last \93known\94 oldest entry 
1052                 // (refreshing on every iteration is probalby not right here) and it has no notion of the 
1053                 // collection size when it is created or while it is operating. It would keep on enumerating 
1054                 // until the index become invalid.  
1055                 //
1056                 // Throwing InvalidOperationException to let you know that your enumerator has been invalidated 
1057                 // because of changes underneath is probably not the most useful behavior.
1058             }
1059             return entry;
1060         }
1061
1062         [ResourceExposure(ResourceScope.None)]
1063         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
1064         private EventLogEntry GetEntryWithOldest(int index) {
1065             EventLogEntry entry = null;
1066             int entryPos = GetCachedEntryPos(index);
1067             if (entryPos >= 0) {
1068                 entry = new EventLogEntry(cache, entryPos, this);
1069                 return entry;
1070             }
1071
1072             string currentMachineName = this.machineName;
1073
1074             // if we haven't seen the one after this, we were probably going
1075             // forwards.
1076             int flags = 0;
1077             if (GetCachedEntryPos(index+1) < 0) {
1078                 flags = NativeMethods.FORWARDS_READ | NativeMethods.SEEK_READ;
1079                 boolFlags[Flag_forwards] = true;
1080             }
1081             else {
1082                 flags = NativeMethods.BACKWARDS_READ | NativeMethods.SEEK_READ;
1083                 boolFlags[Flag_forwards] = false;
1084             }
1085
1086             cache = new byte[BUF_SIZE];
1087             int bytesRead;
1088             int minBytesNeeded;
1089             bool success = UnsafeNativeMethods.ReadEventLog(readHandle, flags, index,
1090                                                   cache, cache.Length, out bytesRead, out minBytesNeeded);
1091             if (!success) {
1092                 int error = Marshal.GetLastWin32Error();
1093                 // NOTE, [....]: ERROR_PROC_NOT_FOUND used to get returned, but I think that
1094                 // was because I was calling GetLastError directly instead of GetLastWin32Error.
1095                 // Making the buffer bigger and trying again seemed to work. I've removed the check
1096                 // for ERROR_PROC_NOT_FOUND because I don't think it's necessary any more, but
1097                 // I can't prove it...
1098                 Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "Error from ReadEventLog is " + error.ToString(CultureInfo.InvariantCulture));
1099                 if (error == NativeMethods.ERROR_INSUFFICIENT_BUFFER || error == NativeMethods.ERROR_EVENTLOG_FILE_CHANGED) {
1100                     if (error == NativeMethods.ERROR_EVENTLOG_FILE_CHANGED) {
1101                         // Reset() sets the cache null.  But since we're going to call ReadEventLog right after this,
1102                         // we need the cache to be something valid.  We'll reuse the old byte array rather
1103                         // than creating a new one.
1104                         byte[] tempcache = cache;
1105                         Reset(currentMachineName);
1106                         cache = tempcache;
1107                     } else {
1108                         // try again with a bigger buffer.
1109                         if (minBytesNeeded > cache.Length) {
1110                             cache = new byte[minBytesNeeded];
1111                         }
1112                     }
1113                     success = UnsafeNativeMethods.ReadEventLog(readHandle, NativeMethods.FORWARDS_READ | NativeMethods.SEEK_READ, index,
1114                                                      cache, cache.Length, out bytesRead, out minBytesNeeded);
1115                 }
1116
1117                 if (!success) {
1118                     throw new InvalidOperationException(SR.GetString(SR.CantReadLogEntryAt, index.ToString(CultureInfo.CurrentCulture)), SharedUtils.CreateSafeWin32Exception());
1119                 }
1120             }
1121             bytesCached = bytesRead;
1122             firstCachedEntry = index;
1123             lastSeenEntry = index;
1124             lastSeenPos = 0;
1125             return new EventLogEntry(cache, 0, this);
1126         }
1127
1128         [ResourceExposure(ResourceScope.Machine)]
1129         [ResourceConsumption(ResourceScope.Machine)]
1130         internal static RegistryKey GetEventLogRegKey(string machine, bool writable) {
1131             RegistryKey lmkey = null;
1132             
1133             try {
1134                 if (machine.Equals(".")) {
1135                     lmkey = Registry.LocalMachine;
1136                 }
1137                 else {
1138                     lmkey = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, machine);
1139
1140                 }
1141                 if (lmkey != null)
1142                     return lmkey.OpenSubKey(EventLogKey, writable);
1143             }
1144             finally {
1145                 if (lmkey != null) lmkey.Close();
1146             }
1147
1148             return null;
1149         }
1150
1151         [ResourceExposure(ResourceScope.Machine)]
1152         [ResourceConsumption(ResourceScope.Machine)]
1153         private RegistryKey GetLogRegKey(string currentMachineName, bool writable) {
1154             string logname = GetLogName(currentMachineName);
1155             // we need to verify the logname here again because we might have tried to look it up
1156             // based on the source and failed.
1157             if (!ValidLogName(logname, false))
1158                 throw new InvalidOperationException(SR.GetString(SR.BadLogName));
1159
1160             RegistryKey eventkey = null;
1161             RegistryKey logkey = null;
1162
1163             try {
1164                 eventkey = GetEventLogRegKey(currentMachineName, false);
1165                 if (eventkey == null)
1166                     throw new InvalidOperationException(SR.GetString(SR.RegKeyMissingShort, EventLogKey, currentMachineName));
1167
1168                 logkey = eventkey.OpenSubKey(logname, writable);
1169                 if (logkey == null)
1170                     throw new InvalidOperationException(SR.GetString(SR.MissingLog, logname, currentMachineName));
1171             }
1172             finally {
1173                 if (eventkey != null) eventkey.Close();
1174             }
1175
1176             return logkey;
1177         }
1178
1179         [ResourceExposure(ResourceScope.None)]
1180         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
1181         private object GetLogRegValue(string currentMachineName, string valuename) {
1182             PermissionSet permissionSet = EventLog._UnsafeGetAssertPermSet();
1183             permissionSet.Assert();
1184             
1185             RegistryKey logkey = null;
1186
1187             try {
1188                 logkey = GetLogRegKey(currentMachineName, false);
1189                 if (logkey == null)
1190                     throw new InvalidOperationException(SR.GetString(SR.MissingLog, GetLogName(currentMachineName), currentMachineName));
1191
1192                 object val = logkey.GetValue(valuename);
1193                 return val;
1194             }
1195             finally {
1196                 if (logkey != null) logkey.Close();
1197
1198                 // Revert registry and environment permission asserts
1199                 CodeAccessPermission.RevertAssert();
1200             }
1201         }
1202
1203         /// <devdoc>
1204         ///     Finds the index into the cache where the next entry starts
1205         /// </devdoc>
1206         private int GetNextEntryPos(int pos) {
1207             return pos + IntFrom(cache, pos);
1208         }
1209
1210         /// <devdoc>
1211         ///     Finds the index into the cache where the previous entry starts
1212         /// </devdoc>
1213         private int GetPreviousEntryPos(int pos) {
1214             // the entries in our buffer come back like this:
1215             // <length 1> ... <data> ...  <length 1> <length 2> ... <data> ... <length 2> ...
1216             // In other words, the length for each entry is repeated at the beginning and
1217             // at the end. This makes it easy to navigate forwards and backwards through
1218             // the buffer.
1219             return pos - IntFrom(cache, pos - 4);
1220         }
1221
1222         [ResourceExposure(ResourceScope.Machine)]
1223         [ResourceConsumption(ResourceScope.Machine)]
1224         internal static string GetDllPath(string machineName) {
1225             return Path.Combine(SharedUtils.GetLatestBuildDllDirectory(machineName), DllName);
1226         }
1227
1228         /// <devdoc>
1229         ///     Extracts a 32-bit integer from the ubyte buffer, beginning at the byte offset
1230         ///     specified in offset.
1231         /// </devdoc>
1232         private static int IntFrom(byte[] buf, int offset) {
1233             // assumes Little Endian byte order.
1234             return(unchecked((int)0xFF000000) & (buf[offset+3] << 24)) | (0xFF0000 & (buf[offset+2] << 16)) |
1235             (0xFF00 & (buf[offset+1] << 8)) | (0xFF & (buf[offset]));
1236         }
1237
1238         [ComVisible(false)]
1239         [ResourceExposure(ResourceScope.None)]
1240         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
1241         public void ModifyOverflowPolicy(OverflowAction action, int retentionDays) {
1242             string currentMachineName = this.machineName;
1243             
1244             EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Administer, currentMachineName);
1245             permission.Demand();
1246
1247             if (action < OverflowAction.DoNotOverwrite || action > OverflowAction.OverwriteOlder)
1248                 throw new InvalidEnumArgumentException("action", (int)action, typeof(OverflowAction));
1249
1250             // this is a long because in the if statement we may need to store values as
1251             // large as UInt32.MaxValue - 1.  This would overflow an int.
1252             long retentionvalue = (long) action;
1253             if (action == OverflowAction.OverwriteOlder) {
1254                 if (retentionDays < 1 || retentionDays > 365)
1255                     throw new ArgumentOutOfRangeException(SR.GetString(SR.RentionDaysOutOfRange));
1256
1257                 retentionvalue = (long) retentionDays * SecondsPerDay;
1258             }
1259
1260             PermissionSet permissionSet = EventLog._UnsafeGetAssertPermSet();
1261             permissionSet.Assert();
1262             
1263             using (RegistryKey logkey = GetLogRegKey(currentMachineName, true))
1264                 logkey.SetValue("Retention", retentionvalue, RegistryValueKind.DWord);
1265         }
1266
1267
1268         /// <devdoc>
1269         ///     Opens the event log with read access
1270         /// </devdoc>
1271         [ResourceExposure(ResourceScope.None)]
1272         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
1273         [SuppressMessage("Microsoft.Security", "CA2103:ReviewImperativeSecurity", Justification = "[....]: Safe, machineName doesn't change")]
1274         private void OpenForRead(string currentMachineName) {
1275             Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::OpenForRead");
1276
1277             EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Administer, currentMachineName);
1278             permission.Demand();
1279
1280             //Cannot allocate the readHandle if the object has been disposed, since finalization has been suppressed.
1281             if (this.boolFlags[Flag_disposed])
1282                 throw new ObjectDisposedException(GetType().Name);
1283
1284             string logname = GetLogName(currentMachineName);
1285
1286             if (logname == null || logname.Length==0)
1287                 throw new ArgumentException(SR.GetString(SR.MissingLogProperty));
1288
1289             if (!EventLog.Exists(logname, currentMachineName) )        // do not open non-existing Log [[....]]
1290                 throw new InvalidOperationException( SR.GetString(SR.LogDoesNotExists, logname, currentMachineName) );
1291             //Check environment before calling api
1292             SharedUtils.CheckEnvironment();
1293
1294             // Clean up cache variables.
1295             // [[....]] The initilizing code is put here to guarantee, that first read of events
1296             //           from log file will start by filling up the cache buffer.
1297             lastSeenEntry = 0;
1298             lastSeenPos = 0;
1299             bytesCached = 0;
1300             firstCachedEntry = -1;
1301
1302             SafeEventLogReadHandle handle = SafeEventLogReadHandle.OpenEventLog(currentMachineName, logname);
1303             if (handle.IsInvalid) {
1304                 Win32Exception e = null;
1305                 if (Marshal.GetLastWin32Error() != 0) {
1306                     e = SharedUtils.CreateSafeWin32Exception();
1307                 }
1308
1309                 throw new InvalidOperationException(SR.GetString(SR.CantOpenLog, logname.ToString(), currentMachineName), e);
1310             }
1311
1312             readHandle = handle;
1313         }
1314
1315         /// <devdoc>
1316         ///     Opens the event log with write access
1317         /// </devdoc>
1318         [ResourceExposure(ResourceScope.None)]
1319         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
1320         private void OpenForWrite(string currentMachineName) {
1321             //Cannot allocate the writeHandle if the object has been disposed, since finalization has been suppressed.
1322             if (this.boolFlags[Flag_disposed])
1323                 throw new ObjectDisposedException(GetType().Name);
1324
1325             Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::OpenForWrite");
1326             if (sourceName == null || sourceName.Length==0)
1327                 throw new ArgumentException(SR.GetString(SR.NeedSourceToOpen));
1328
1329             //Check environment before calling api
1330             SharedUtils.CheckEnvironment();
1331
1332             SafeEventLogWriteHandle handle = SafeEventLogWriteHandle.RegisterEventSource(currentMachineName, sourceName);
1333             if (handle.IsInvalid) {
1334                 Win32Exception e = null;
1335                 if (Marshal.GetLastWin32Error() != 0) {
1336                     e = SharedUtils.CreateSafeWin32Exception();
1337                 }
1338                 throw new InvalidOperationException(SR.GetString(SR.CantOpenLogAccess, sourceName), e);
1339             }
1340
1341             writeHandle = handle;
1342         }
1343
1344         [ComVisible(false)]
1345         [ResourceExposure(ResourceScope.Machine)]
1346         [ResourceConsumption(ResourceScope.Machine)]
1347         public void RegisterDisplayName(string resourceFile, long resourceId) {
1348             string currentMachineName = this.machineName;
1349
1350             EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Administer, currentMachineName);
1351             permission.Demand();
1352
1353             PermissionSet permissionSet = EventLog._UnsafeGetAssertPermSet();
1354             permissionSet.Assert();
1355
1356             using (RegistryKey logkey = GetLogRegKey(currentMachineName, true)) {
1357                 logkey.SetValue("DisplayNameFile", resourceFile, RegistryValueKind.ExpandString);
1358                 logkey.SetValue("DisplayNameID", resourceId, RegistryValueKind.DWord);
1359             }
1360         }
1361
1362         private void Reset(string currentMachineName) {
1363             Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::Reset");
1364             // save the state we're in now
1365             bool openRead = IsOpenForRead;
1366             bool openWrite = IsOpenForWrite;
1367             bool isMonitoring = boolFlags[Flag_monitoring];
1368             bool isListening = boolFlags[Flag_registeredAsListener];
1369
1370             // close everything down
1371             Close(currentMachineName);
1372             cache = null;
1373
1374             // and get us back into the same state as before
1375             if (openRead)
1376                 OpenForRead(currentMachineName);
1377             if (openWrite)
1378                 OpenForWrite(currentMachineName);
1379             if (isListening)
1380                 StartListening(currentMachineName, GetLogName(currentMachineName));
1381             
1382             boolFlags[Flag_monitoring] = isMonitoring;
1383         }
1384
1385         [HostProtection(Synchronization=true)]
1386         private static void RemoveListenerComponent(EventLogInternal component, string compLogName) {
1387             lock (InternalSyncObject) {
1388                 Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::RemoveListenerComponent(" + compLogName + ")");
1389
1390                 LogListeningInfo info = (LogListeningInfo) listenerInfos[compLogName];
1391                 Debug.Assert(info != null);
1392
1393                 // remove the requested component from the list.
1394                 info.listeningComponents.Remove(component);
1395                 if (info.listeningComponents.Count != 0)
1396                     return;
1397
1398                 // if that was the last interested compononent, destroy the handles and stop listening.
1399
1400                 info.handleOwner.Dispose();
1401
1402                 //Unregister the thread pool wait handle
1403                 info.registeredWaitHandle.Unregister(info.waitHandle);
1404                 // close the handle
1405                 info.waitHandle.Close();
1406
1407                 listenerInfos[compLogName] = null;
1408             }
1409         }
1410       
1411         /// <devdoc>
1412         ///     Sets up the event monitoring mechanism.  We don't track event log changes
1413         ///     unless someone is interested, so we set this up on demand.
1414         /// </devdoc>
1415         [HostProtection(Synchronization=true, ExternalThreading=true)]
1416         private void StartListening(string currentMachineName, string currentLogName) {
1417             // make sure we don't fire events for entries that are already there
1418             Debug.Assert(!boolFlags[Flag_registeredAsListener], "StartListening called with boolFlags[Flag_registeredAsListener] true.");
1419             lastSeenCount = EntryCount + OldestEntryNumber;
1420             Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::StartListening: lastSeenCount = " + lastSeenCount);
1421             AddListenerComponent(this, currentMachineName, currentLogName);
1422             boolFlags[Flag_registeredAsListener] = true;
1423         }
1424
1425         private void StartRaisingEvents(string currentMachineName, string currentLogName) {
1426             if (!boolFlags[Flag_initializing] && !boolFlags[Flag_monitoring] && !parent.ComponentDesignMode) {
1427                 StartListening(currentMachineName, currentLogName);
1428             }
1429             boolFlags[Flag_monitoring] = true;
1430         }
1431
1432         private static void StaticCompletionCallback(object context, bool wasSignaled) {
1433             
1434             LogListeningInfo info = (LogListeningInfo) context;
1435             if (info == null)
1436                 return;
1437
1438             // get a snapshot of the components to fire the event on
1439             EventLogInternal[] interestedComponents;
1440             lock (InternalSyncObject) {
1441                 interestedComponents = (EventLogInternal[])info.listeningComponents.ToArray(typeof(EventLogInternal));
1442             }
1443
1444             Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::StaticCompletionCallback: notifying " + interestedComponents.Length + " components.");
1445
1446             for (int i = 0; i < interestedComponents.Length; i++) {
1447                 try {
1448                     if (interestedComponents[i] != null) {
1449                         interestedComponents[i].CompletionCallback(null);
1450                     }
1451                 } catch (ObjectDisposedException) {
1452                     // The EventLog that was registered to listen has been disposed.  Nothing much we can do here
1453                     // we don't want to propigate this error up as it will likely be unhandled and will cause the app
1454                     // to crash.
1455                     Debug.WriteLineIf(CompModSwitches.EventLog.TraceVerbose, "EventLog::StaticCompletionCallback: ignored an ObjectDisposedException");
1456                 }
1457             }
1458         }
1459
1460         /// <devdoc>
1461         ///     Tears down the event listening mechanism.  This is called when the last
1462         ///     interested party removes their event handler.
1463         /// </devdoc>
1464         [HostProtection(Synchronization=true, ExternalThreading=true)]
1465         private void StopListening(/*string currentMachineName,*/ string currentLogName) {
1466             Debug.Assert(boolFlags[Flag_registeredAsListener], "StopListening called without StartListening.");
1467             RemoveListenerComponent(this, currentLogName);
1468             boolFlags[Flag_registeredAsListener] = false;
1469         }
1470
1471         /// <devdoc>
1472         /// </devdoc>
1473         private void StopRaisingEvents(/*string currentMachineName,*/ string currentLogName) {
1474             if (!boolFlags[Flag_initializing] && boolFlags[Flag_monitoring] && !parent.ComponentDesignMode) {
1475                 StopListening(currentLogName);
1476             }
1477             boolFlags[Flag_monitoring] = false;
1478         }
1479
1480         // CharIsPrintable used to be Char.IsPrintable, but Jay removed it and
1481         // is forcing people to use the Unicode categories themselves.  Copied
1482         // the code here.  
1483         private static bool CharIsPrintable(char c) {
1484             UnicodeCategory uc = Char.GetUnicodeCategory(c);
1485             return (!(uc == UnicodeCategory.Control) || (uc == UnicodeCategory.Format) ||
1486                     (uc == UnicodeCategory.LineSeparator) || (uc == UnicodeCategory.ParagraphSeparator) ||
1487             (uc == UnicodeCategory.OtherNotAssigned));
1488         }
1489
1490         // SECREVIEW: Make sure this method catches all the strange cases.
1491         internal static bool ValidLogName(string logName, bool ignoreEmpty) {
1492             // No need to trim here since the next check will verify that there are no spaces.
1493             // We need to ignore the empty string as an invalid log name sometimes because it can
1494             // be passed in from our default constructor.
1495             if (logName.Length == 0 && !ignoreEmpty)
1496                 return false;
1497
1498             //any space, backslash, asterisk, or question mark is bad
1499             //any non-printable characters are also bad
1500             foreach (char c in logName)
1501                 if (!CharIsPrintable(c) || (c == '\\') || (c == '*') || (c == '?'))
1502                     return false;
1503
1504             return true;
1505         }
1506
1507         [ResourceExposure(ResourceScope.None)]
1508         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
1509         [SuppressMessage("Microsoft.Security", "CA2103:ReviewImperativeSecurity", Justification = "[....]: Safe, machineName doesn't change")]
1510         private void VerifyAndCreateSource(string sourceName, string currentMachineName) {
1511             if (boolFlags[Flag_sourceVerified]) 
1512                 return;
1513             
1514             if (!EventLog.SourceExists(sourceName, currentMachineName, true)) {
1515                 Mutex mutex = null;
1516                 RuntimeHelpers.PrepareConstrainedRegions();
1517                 try {
1518                     SharedUtils.EnterMutex(eventLogMutexName, ref mutex);
1519                     if (!EventLog.SourceExists(sourceName, currentMachineName, true)) {
1520                         if (GetLogName(currentMachineName) == null)
1521                             this.logName = "Application";
1522                         // we automatically add an entry in the registry if there's not already
1523                         // one there for this source
1524                         EventLog.CreateEventSource(new EventSourceCreationData(sourceName, GetLogName(currentMachineName), currentMachineName));
1525                         // The user may have set a custom log and tried to read it before trying to
1526                         // write. Due to a quirk in the event log API, we would have opened the Application
1527                         // log to read (because the custom log wasn't there). Now that we've created
1528                         // the custom log, we should close so that when we re-open, we get a read
1529                         // handle on the _new_ log instead of the Application log.
1530                         Reset(currentMachineName);
1531                     }
1532                     else {
1533                         string rightLogName = EventLog.LogNameFromSourceName(sourceName, currentMachineName);
1534                         string currentLogName = GetLogName(currentMachineName);
1535                         if (rightLogName != null && currentLogName != null && String.Compare(rightLogName, currentLogName, StringComparison.OrdinalIgnoreCase) != 0)
1536                             throw new ArgumentException(SR.GetString(SR.LogSourceMismatch, Source.ToString(), currentLogName, rightLogName));
1537                     }
1538                     
1539                 }
1540                 finally {
1541                     if (mutex != null) {
1542                         mutex.ReleaseMutex();
1543                         mutex.Close();
1544                     }
1545                 }
1546             }
1547             else {
1548                 EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Write, currentMachineName);
1549                 permission.Demand();
1550
1551                 string rightLogName = EventLog._InternalLogNameFromSourceName(sourceName, currentMachineName);
1552                 string currentLogName = GetLogName(currentMachineName);
1553                 if (rightLogName != null && currentLogName != null && String.Compare(rightLogName, currentLogName, StringComparison.OrdinalIgnoreCase) != 0)
1554                     throw new ArgumentException(SR.GetString(SR.LogSourceMismatch, Source.ToString(), currentLogName, rightLogName));
1555             }
1556             
1557             boolFlags[Flag_sourceVerified] = true;
1558         }
1559
1560         /// <devdoc>
1561         ///    <para>
1562         ///       Writes an information type entry with the given message text to the event log.
1563         ///    </para>
1564         /// </devdoc>
1565         public void WriteEntry(string message) {
1566             WriteEntry(message, EventLogEntryType.Information, (short) 0, 0, null);
1567         }
1568
1569         /// <devdoc>
1570         ///    <para>
1571         ///       Writes an entry of the specified <see cref='System.Diagnostics.EventLogEntryType'/> to the event log. Valid types are
1572         ///    <see langword='Error'/>, <see langword='Warning'/>, <see langword='Information'/>,
1573         ///    <see langword='Success Audit'/>, and <see langword='Failure Audit'/>.
1574         ///    </para>
1575         /// </devdoc>
1576         public void WriteEntry(string message, EventLogEntryType type) {
1577             WriteEntry(message, type, (short) 0, 0, null);
1578         }
1579
1580         /// <devdoc>
1581         ///    <para>
1582         ///       Writes an entry of the specified <see cref='System.Diagnostics.EventLogEntryType'/>
1583         ///       and with the
1584         ///       user-defined <paramref name="eventID"/>
1585         ///       to
1586         ///       the event log.
1587         ///    </para>
1588         /// </devdoc>
1589         public void WriteEntry(string message, EventLogEntryType type, int eventID) {
1590             WriteEntry(message, type, eventID, 0, null);
1591         }
1592
1593         /// <devdoc>
1594         ///    <para>
1595         ///       Writes an entry of the specified type with the
1596         ///       user-defined <paramref name="eventID"/> and <paramref name="category"/>
1597         ///       to the event log. The <paramref name="category"/>
1598         ///       can be used by the event viewer to filter events in the log.
1599         ///    </para>
1600         /// </devdoc>
1601         public void WriteEntry(string message, EventLogEntryType type, int eventID, short category) {
1602             WriteEntry(message, type, eventID, category, null);
1603         }
1604
1605         /// <devdoc>
1606         ///    <para>
1607         ///       Writes an entry of the specified type with the
1608         ///       user-defined <paramref name="eventID"/> and <paramref name="category"/> to the event log, and appends binary data to
1609         ///       the message. The Event Viewer does not interpret this data; it
1610         ///       displays raw data only in a combined hexadecimal and text format.
1611         ///    </para>
1612         /// </devdoc>
1613         public void WriteEntry(string message, EventLogEntryType type, int eventID, short category,
1614                                byte[] rawData) {
1615
1616             if (eventID < 0 || eventID > ushort.MaxValue)
1617                 throw new ArgumentException(SR.GetString(SR.EventID, eventID, 0, (int)ushort.MaxValue));
1618
1619             if (Source.Length == 0)
1620                 throw new ArgumentException(SR.GetString(SR.NeedSourceToWrite));
1621
1622             if (!Enum.IsDefined(typeof(EventLogEntryType), type))
1623                 throw new InvalidEnumArgumentException("type", (int)type, typeof(EventLogEntryType));
1624
1625             string currentMachineName = machineName;
1626             if (!boolFlags[Flag_writeGranted]) {
1627                 EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Write, currentMachineName);
1628                 permission.Demand();
1629                 boolFlags[Flag_writeGranted] = true;
1630             }
1631
1632             VerifyAndCreateSource(sourceName, currentMachineName);
1633
1634             // now that the source has been hooked up to our DLL, we can use "normal"
1635             // (message-file driven) logging techniques.
1636             // Our DLL has 64K different entries; all of them just display the first
1637             // insertion string.
1638             InternalWriteEvent((uint)eventID, (ushort)category, type, new string[] { message}, rawData, currentMachineName);
1639         }
1640
1641         [ComVisible(false)]
1642         public void WriteEvent(EventInstance instance, params Object[] values) {
1643             WriteEvent(instance, null, values);
1644         }
1645
1646         [ComVisible(false)]
1647         public void WriteEvent(EventInstance instance, byte[] data, params Object[] values) {
1648             if (instance == null)
1649                 throw new ArgumentNullException("instance");
1650             if (Source.Length == 0)
1651                 throw new ArgumentException(SR.GetString(SR.NeedSourceToWrite));
1652
1653             string currentMachineName = machineName;
1654             if (!boolFlags[Flag_writeGranted]) {
1655                 EventLogPermission permission = new EventLogPermission(EventLogPermissionAccess.Write, currentMachineName);
1656                 permission.Demand();
1657                 boolFlags[Flag_writeGranted] = true;
1658             }
1659
1660             VerifyAndCreateSource(Source, currentMachineName);
1661
1662             string[] strings = null;
1663             
1664             if (values != null) {
1665                 strings = new string[values.Length];
1666                 for (int i=0; i<values.Length; i++) {
1667                     if (values[i] != null)
1668                         strings[i] = values[i].ToString();
1669                     else
1670                         strings[i] = String.Empty;
1671                 }
1672             }
1673             
1674             InternalWriteEvent((uint) instance.InstanceId, (ushort) instance.CategoryId, instance.EntryType, strings, data, currentMachineName);
1675         }
1676
1677         [ResourceExposure(ResourceScope.None)]
1678         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
1679         private void InternalWriteEvent(uint eventID, ushort category, EventLogEntryType type, string[] strings,
1680                                 byte[] rawData, string currentMachineName) {
1681
1682             // check arguments
1683             if (strings == null)
1684                 strings = new string[0];
1685             if (strings.Length >= 256)
1686                 throw new ArgumentException(SR.GetString(SR.TooManyReplacementStrings));
1687             
1688             for (int i = 0; i < strings.Length; i++) {
1689                 if (strings[i] == null)
1690                     strings[i] = String.Empty;
1691
1692                 // make sure the strings aren't too long.  MSDN says each string has a limit of 32k (32768) characters, but 
1693                 // experimentation shows that it doesn't like anything larger than 32766
1694                 if (strings[i].Length > 32766)
1695                     throw new ArgumentException(SR.GetString(SR.LogEntryTooLong));
1696             }
1697             if (rawData == null)
1698                 rawData = new byte[0];
1699
1700             if (Source.Length == 0)
1701                 throw new ArgumentException(SR.GetString(SR.NeedSourceToWrite));
1702
1703             if (!IsOpenForWrite)
1704                 OpenForWrite(currentMachineName);
1705
1706             // pin each of the strings in memory
1707             IntPtr[] stringRoots = new IntPtr[strings.Length];
1708             GCHandle[] stringHandles = new GCHandle[strings.Length];
1709             GCHandle stringsRootHandle = GCHandle.Alloc(stringRoots, GCHandleType.Pinned);
1710             try {
1711                 for (int strIndex = 0; strIndex < strings.Length; strIndex++) {
1712                     stringHandles[strIndex] = GCHandle.Alloc(strings[strIndex], GCHandleType.Pinned);
1713                     stringRoots[strIndex] = stringHandles[strIndex].AddrOfPinnedObject();
1714                 }
1715
1716                 byte[] sid = null;
1717                 // actually report the event
1718                 bool success = UnsafeNativeMethods.ReportEvent(writeHandle, (short) type, category, eventID,
1719                                                      sid, (short) strings.Length, rawData.Length, new HandleRef(this, stringsRootHandle.AddrOfPinnedObject()), rawData);
1720                 if (!success) {
1721                     //Trace("WriteEvent", "Throwing Win32Exception");
1722                     throw SharedUtils.CreateSafeWin32Exception();
1723                 }
1724             }
1725             finally {
1726                 // now free the pinned strings
1727                 for (int i = 0; i < strings.Length; i++) {
1728                     if (stringHandles[i].IsAllocated)
1729                         stringHandles[i].Free();
1730                 }
1731                 stringsRootHandle.Free();
1732             }
1733         }
1734
1735         private class LogListeningInfo {
1736             public EventLogInternal handleOwner;
1737             public RegisteredWaitHandle registeredWaitHandle;
1738             public WaitHandle waitHandle;
1739             public ArrayList listeningComponents = new ArrayList();
1740         }
1741
1742     }
1743
1744 }