2010-06-21 Marek Habersack <mhabersack@novell.com>
[mcs.git] / class / System / System.Diagnostics / LocalFileEventLog.cs
blob62563821fe686421a2bb243f99431825b6d0882b
1 //
2 // System.Diagnostics.LocalFileEventLog.cs
3 //
4 // Author:
5 // Atsushi Enomoto <atsushi@ximian.com>
6 // Gert Driesen <drieseng@users.sourceforge.net>
7 //
8 // Copyright (C) 2006 Novell, Inc (http://www.novell.com)
9 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 //
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 //
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 using System;
32 using System.Collections;
33 using System.ComponentModel;
34 using System.Diagnostics;
35 using System.Globalization;
36 using System.IO;
37 using System.Runtime.InteropServices;
38 using System.Security;
39 using System.Text;
40 using System.Threading;
42 namespace System.Diagnostics
44 internal class LocalFileEventLog : EventLogImpl
46 const string DateFormat = "yyyyMMddHHmmssfff";
47 static readonly object lockObject = new object ();
48 FileSystemWatcher file_watcher;
49 int last_notification_index;
50 bool _notifying;
52 public LocalFileEventLog (EventLog coreEventLog) : base (coreEventLog)
56 public override void BeginInit () {
59 public override void Clear ()
61 string logDir = FindLogStore (CoreEventLog.Log);
62 if (!Directory.Exists (logDir))
63 return;
65 foreach (string file in Directory.GetFiles (logDir, "*.log"))
66 File.Delete (file);
69 public override void Close ()
71 if (file_watcher != null) {
72 file_watcher.EnableRaisingEvents = false;
73 file_watcher = null; // force creation of new FileSystemWatcher
77 public override void CreateEventSource (EventSourceCreationData sourceData)
79 // construct path for storing log entries
80 string logDir = FindLogStore (sourceData.LogName);
81 // create event log store (if necessary), and modify access
82 // permissions (unix only)
83 if (!Directory.Exists (logDir)) {
84 // ensure the log name is valid for customer logs
85 ValidateCustomerLogName (sourceData.LogName, sourceData.MachineName);
87 Directory.CreateDirectory (logDir);
88 // MS does not allow an event source to be named after an already
89 // existing event log. To speed up checking whether a given event
90 // source already exists (either as a event source or event log)
91 // we create an event source directory named after the event log.
92 // This matches what MS does with the registry-based registration.
93 Directory.CreateDirectory (Path.Combine (logDir, sourceData.LogName));
94 if (RunningOnUnix) {
95 ModifyAccessPermissions (logDir, "777");
96 ModifyAccessPermissions (logDir, "+t");
99 // create directory for event source, so we can check if the event
100 // source already exists
101 string sourceDir = Path.Combine (logDir, sourceData.Source);
102 Directory.CreateDirectory (sourceDir);
105 public override void Delete (string logName, string machineName)
107 string logDir = FindLogStore (logName);
108 if (!Directory.Exists (logDir))
109 throw new InvalidOperationException (string.Format (
110 CultureInfo.InvariantCulture, "Event Log '{0}'"
111 + " does not exist on computer '{1}'.", logName,
112 machineName));
114 Directory.Delete (logDir, true);
117 public override void DeleteEventSource (string source, string machineName)
119 if (!Directory.Exists (EventLogStore))
120 throw new ArgumentException (string.Format (
121 CultureInfo.InvariantCulture, "The source '{0}' is not"
122 + " registered on computer '{1}'.", source, machineName));
124 string sourceDir = FindSourceDirectory (source);
125 if (sourceDir == null)
126 throw new ArgumentException (string.Format (
127 CultureInfo.InvariantCulture, "The source '{0}' is not"
128 + " registered on computer '{1}'.", source, machineName));
129 Directory.Delete (sourceDir);
132 public override void Dispose (bool disposing)
134 Close ();
137 public override void DisableNotification ()
139 if (file_watcher == null)
140 return;
141 file_watcher.EnableRaisingEvents = false;
144 public override void EnableNotification ()
146 if (file_watcher == null) {
147 string logDir = FindLogStore (CoreEventLog.Log);
148 if (!Directory.Exists (logDir))
149 Directory.CreateDirectory (logDir);
151 file_watcher = new FileSystemWatcher ();
152 file_watcher.Path = logDir;
153 file_watcher.Created += delegate (object o, FileSystemEventArgs e) {
154 lock (this) {
155 if (_notifying)
156 return;
157 _notifying = true;
160 // allow for file to be finished writing
161 Thread.Sleep (100);
163 // Process every new entry in one notification event.
164 try {
165 while (GetLatestIndex () > last_notification_index) {
166 try {
167 CoreEventLog.OnEntryWritten (GetEntry (last_notification_index++));
168 } catch (Exception ex) {
169 // FIXME: find some proper way to output this error
170 Debug.WriteLine (ex);
173 } finally {
174 lock (this)
175 _notifying = false;
179 last_notification_index = GetLatestIndex ();
180 file_watcher.EnableRaisingEvents = true;
183 public override void EndInit () { }
185 public override bool Exists (string logName, string machineName)
187 string logDir = FindLogStore (logName);
188 return Directory.Exists (logDir);
191 [MonoTODO ("Use MessageTable from PE for lookup")]
192 protected override string FormatMessage (string source, uint eventID, string [] replacementStrings)
194 return string.Join (", ", replacementStrings);
197 protected override int GetEntryCount ()
199 string logDir = FindLogStore (CoreEventLog.Log);
200 if (!Directory.Exists (logDir))
201 return 0;
203 string[] logFiles = Directory.GetFiles (logDir, "*.log");
204 return logFiles.Length;
207 protected override EventLogEntry GetEntry (int index)
209 string logDir = FindLogStore (CoreEventLog.Log);
211 // our file names are one-based
212 string file = Path.Combine (logDir, (index + 1).ToString (
213 CultureInfo.InvariantCulture) + ".log");
215 using (TextReader tr = File.OpenText (file)) {
216 int eventIndex = int.Parse (Path.GetFileNameWithoutExtension (file),
217 CultureInfo.InvariantCulture);
218 uint instanceID = uint.Parse (tr.ReadLine ().Substring (12),
219 CultureInfo.InvariantCulture);
220 EventLogEntryType type = (EventLogEntryType)
221 Enum.Parse (typeof (EventLogEntryType), tr.ReadLine ().Substring (11));
222 string source = tr.ReadLine ().Substring (8);
223 string category = tr.ReadLine ().Substring (10);
224 short categoryNumber = short.Parse(category, CultureInfo.InvariantCulture);
225 string categoryName = "(" + category + ")";
226 DateTime timeGenerated = DateTime.ParseExact (tr.ReadLine ().Substring (15),
227 DateFormat, CultureInfo.InvariantCulture);
228 DateTime timeWritten = File.GetLastWriteTime (file);
229 int stringNums = int.Parse (tr.ReadLine ().Substring (20));
230 ArrayList replacementTemp = new ArrayList ();
231 StringBuilder sb = new StringBuilder ();
232 while (replacementTemp.Count < stringNums) {
233 char c = (char) tr.Read ();
234 if (c == '\0') {
235 replacementTemp.Add (sb.ToString ());
236 sb.Length = 0;
237 } else {
238 sb.Append (c);
241 string [] replacementStrings = new string [replacementTemp.Count];
242 replacementTemp.CopyTo (replacementStrings, 0);
244 string message = FormatMessage (source, instanceID, replacementStrings);
245 int eventID = EventLog.GetEventID (instanceID);
247 byte [] bin = Convert.FromBase64String (tr.ReadToEnd ());
248 return new EventLogEntry (categoryName, categoryNumber, eventIndex,
249 eventID, source, message, null, Environment.MachineName,
250 type, timeGenerated, timeWritten, bin, replacementStrings,
251 instanceID);
255 [MonoTODO]
256 protected override string GetLogDisplayName ()
258 return CoreEventLog.Log;
261 protected override string [] GetLogNames (string machineName)
263 if (!Directory.Exists (EventLogStore))
264 return new string [0];
266 string [] logDirs = Directory.GetDirectories (EventLogStore, "*");
267 string [] logNames = new string [logDirs.Length];
268 for (int i = 0; i < logDirs.Length; i++)
269 logNames [i] = Path.GetFileName (logDirs [i]);
270 return logNames;
273 public override string LogNameFromSourceName (string source, string machineName)
275 if (!Directory.Exists (EventLogStore))
276 return string.Empty;
278 string sourceDir = FindSourceDirectory (source);
279 if (sourceDir == null)
280 return string.Empty;
281 DirectoryInfo info = new DirectoryInfo (sourceDir);
282 return info.Parent.Name;
285 public override bool SourceExists (string source, string machineName)
287 if (!Directory.Exists (EventLogStore))
288 return false;
289 string sourceDir = FindSourceDirectory (source);
290 return (sourceDir != null);
293 public override void WriteEntry (string [] replacementStrings, EventLogEntryType type, uint instanceID, short category, byte [] rawData)
295 lock (lockObject) {
296 string logDir = FindLogStore (CoreEventLog.Log);
298 int index = GetLatestIndex () + 1;
299 string logPath = Path.Combine (logDir, index.ToString (CultureInfo.InvariantCulture) + ".log");
300 try {
301 using (TextWriter w = File.CreateText (logPath)) {
302 #if NET_2_0
303 w.WriteLine ("InstanceID: {0}", instanceID.ToString (CultureInfo.InvariantCulture));
304 #else
305 w.WriteLine ("InstanceID: {0}", instanceID.ToString (CultureInfo.InvariantCulture));
306 #endif
307 w.WriteLine ("EntryType: {0}", (int) type);
308 w.WriteLine ("Source: {0}", CoreEventLog.Source);
309 w.WriteLine ("Category: {0}", category.ToString (CultureInfo.InvariantCulture));
310 w.WriteLine ("TimeGenerated: {0}", DateTime.Now.ToString (
311 DateFormat, CultureInfo.InvariantCulture));
312 w.WriteLine ("ReplacementStrings: {0}", replacementStrings.
313 Length.ToString (CultureInfo.InvariantCulture));
314 StringBuilder sb = new StringBuilder ();
315 for (int i = 0; i < replacementStrings.Length; i++) {
316 string replacement = replacementStrings [i];
317 sb.Append (replacement);
318 sb.Append ('\0');
320 w.Write (sb.ToString ());
321 w.Write (Convert.ToBase64String (rawData));
323 } catch (IOException) {
324 File.Delete (logPath);
329 private string FindSourceDirectory (string source)
331 string sourceDir = null;
333 string [] logDirs = Directory.GetDirectories (EventLogStore, "*");
334 for (int i = 0; i < logDirs.Length; i++) {
335 string [] sourceDirs = Directory.GetDirectories (logDirs [i], "*");
336 for (int j = 0; j < sourceDirs.Length; j++) {
337 string relativeDir = Path.GetFileName (sourceDirs [j]);
338 // use a case-insensitive comparison
339 if (string.Compare (relativeDir, source, true, CultureInfo.InvariantCulture) == 0) {
340 sourceDir = sourceDirs [j];
341 break;
345 return sourceDir;
348 private bool RunningOnUnix {
349 get {
350 int p = (int) Environment.OSVersion.Platform;
351 return ((p == 4) || (p == 128) || (p == 6));
355 private string FindLogStore (string logName) {
356 // when the event log store does not yet exist, there's no need
357 // to perform a case-insensitive lookup
358 if (!Directory.Exists (EventLogStore))
359 return Path.Combine (EventLogStore, logName);
361 // we'll use a case-insensitive lookup to match the MS behaviour
362 // while still allowing the original casing of the log name to be
363 // retained
364 string [] logDirs = Directory.GetDirectories (EventLogStore, "*");
365 for (int i = 0; i < logDirs.Length; i++) {
366 string relativeDir = Path.GetFileName (logDirs [i]);
367 // use a case-insensitive comparison
368 if (string.Compare (relativeDir, logName, true, CultureInfo.InvariantCulture) == 0) {
369 return logDirs [i];
373 return Path.Combine (EventLogStore, logName);
376 private string EventLogStore {
377 get {
378 // for the local file implementation, the MONO_EVENTLOG_TYPE
379 // environment variable can contain the path of the event log
380 // store by using the following syntax: local:<path>
381 string eventLogType = Environment.GetEnvironmentVariable (EventLog.EVENTLOG_TYPE_VAR);
382 if (eventLogType != null && eventLogType.Length > EventLog.LOCAL_FILE_IMPL.Length + 1)
383 return eventLogType.Substring (EventLog.LOCAL_FILE_IMPL.Length + 1);
384 if (RunningOnUnix) {
385 return "/var/lib/mono/eventlog";
386 } else {
387 return Path.Combine (Environment.GetFolderPath (
388 Environment.SpecialFolder.CommonApplicationData),
389 "mono\\eventlog");
394 private int GetLatestIndex () {
395 // our file names are one-based
396 int maxIndex = 0;
397 string[] logFiles = Directory.GetFiles (FindLogStore (CoreEventLog.Log), "*.log");
398 for (int i = 0; i < logFiles.Length; i++) {
399 try {
400 string file = logFiles[i];
401 int index = int.Parse (Path.GetFileNameWithoutExtension (
402 file), CultureInfo.InvariantCulture);
403 if (index > maxIndex)
404 maxIndex = index;
405 } catch {
408 return maxIndex;
411 private static void ModifyAccessPermissions (string path, string permissions)
413 ProcessStartInfo pi = new ProcessStartInfo ();
414 pi.FileName = "chmod";
415 pi.RedirectStandardOutput = true;
416 pi.RedirectStandardError = true;
417 pi.UseShellExecute = false;
418 pi.Arguments = string.Format ("{0} \"{1}\"", permissions, path);
420 Process p = null;
421 try {
422 p = Process.Start (pi);
423 } catch (Exception ex) {
424 throw new SecurityException ("Access permissions could not be modified.", ex);
427 p.WaitForExit ();
428 if (p.ExitCode != 0) {
429 p.Close ();
430 throw new SecurityException ("Access permissions could not be modified.");
432 p.Close ();
435 #if NET_2_0
436 public override OverflowAction OverflowAction {
437 get { return OverflowAction.DoNotOverwrite; }
440 public override int MinimumRetentionDays {
441 get { return int.MaxValue; }
444 public override long MaximumKilobytes {
445 get { return long.MaxValue; }
446 set { throw new NotSupportedException ("This EventLog implementation does not support setting max kilobytes policy"); }
449 public override void ModifyOverflowPolicy (OverflowAction action, int retentionDays)
451 throw new NotSupportedException ("This EventLog implementation does not support modifying overflow policy");
454 public override void RegisterDisplayName (string resourceFile, long resourceId)
456 throw new NotSupportedException ("This EventLog implementation does not support registering display name");
458 #endif