* DiagnosticsConfigurationHandler.cs: Rename ValidateIntegralValue
[mono.git] / mcs / class / System / System.Diagnostics / LocalFileEventLog.cs
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 //
10 //
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.
29 //
30
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
41 namespace System.Diagnostics
42 {
43         internal class LocalFileEventLog : EventLogImpl
44         {
45                 const string DateFormat = "yyyyMMddHHmmssfff";
46                 static readonly object lockObject = new object ();
47                 FileSystemWatcher file_watcher;
48                 int last_notification_index;
49                 bool _notifying;
50
51                 public LocalFileEventLog (EventLog coreEventLog) : base (coreEventLog)
52                 {
53                 }
54
55                 public override void BeginInit () {
56                 }
57
58                 public override void Clear ()
59                 {
60                         string logDir = FindLogStore (CoreEventLog.Log);
61                         if (!Directory.Exists (logDir))
62                                 return;
63
64                         foreach (string file in Directory.GetFiles (logDir, "*.log"))
65                                 File.Delete (file);
66                 }
67
68                 public override void Close ()
69                 {
70                         if (file_watcher != null) {
71                                 file_watcher.EnableRaisingEvents = false;
72                                 file_watcher = null; // force creation of new FileSystemWatcher
73                         }
74                 }
75
76                 public override void CreateEventSource (EventSourceCreationData sourceData)
77                 {
78                         // construct path for storing log entries
79                         string logDir = FindLogStore (sourceData.LogName);
80                         // create event log store (if necessary), and modify access
81                         // permissions (unix only)
82                         if (!Directory.Exists (logDir)) {
83                                 // ensure the log name is valid for customer logs
84                                 ValidateCustomerLogName (sourceData.LogName, sourceData.MachineName);
85
86                                 Directory.CreateDirectory (logDir);
87                                 // MS does not allow an event source to be named after an already
88                                 // existing event log. To speed up checking whether a given event
89                                 // source already exists (either as a event source or event log)
90                                 // we create an event source directory named after the event log.
91                                 // This matches what MS does with the registry-based registration.
92                                 Directory.CreateDirectory (Path.Combine (logDir, sourceData.LogName));
93                                 if (RunningOnLinux) {
94                                         ModifyAccessPermissions (logDir, "777");
95                                         ModifyAccessPermissions (logDir, "+t");
96                                 }
97                         }
98                         // create directory for event source, so we can check if the event
99                         // source already exists
100                         string sourceDir = Path.Combine (logDir, sourceData.Source);
101                         Directory.CreateDirectory (sourceDir);
102                 }
103
104                 public override void Delete (string logName, string machineName)
105                 {
106                         string logDir = FindLogStore (logName);
107                         if (!Directory.Exists (logDir))
108                                 throw new InvalidOperationException (string.Format (
109                                         CultureInfo.InvariantCulture, "Event Log '{0}'"
110                                         + " does not exist on computer '{1}'.", logName,
111                                         machineName));
112
113                         Directory.Delete (logDir, true);
114                 }
115
116                 public override void DeleteEventSource (string source, string machineName)
117                 {
118                         if (!Directory.Exists (EventLogStore))
119                                 throw new ArgumentException (string.Format (
120                                         CultureInfo.InvariantCulture, "The source '{0}' is not"
121                                         + " registered on computer '{1}'.", source, machineName));
122
123                         string sourceDir = FindSourceDirectory (source);
124                         if (sourceDir == null)
125                                 throw new ArgumentException (string.Format (
126                                         CultureInfo.InvariantCulture, "The source '{0}' is not"
127                                         + " registered on computer '{1}'.", source, machineName));
128                         Directory.Delete (sourceDir);
129                 }
130
131                 public override void Dispose (bool disposing)
132                 {
133                         Close ();
134                 }
135
136                 public override void DisableNotification ()
137                 {
138                         if (file_watcher == null)
139                                 return;
140                         file_watcher.EnableRaisingEvents = false;
141                 }
142
143                 public override void EnableNotification ()
144                 {
145                         if (file_watcher == null) {
146                                 string logDir = FindLogStore (CoreEventLog.Log);
147                                 if (!Directory.Exists (logDir))
148                                         Directory.CreateDirectory (logDir);
149
150                                 file_watcher = new FileSystemWatcher ();
151                                 file_watcher.Path = logDir;
152                                 file_watcher.Created += delegate (object o, FileSystemEventArgs e) {
153                                         lock (this) {
154                                                 if (_notifying)
155                                                         return;
156                                                 _notifying = true;
157                                         }
158
159                                         // Process every new entry in one notification event.
160                                         try {
161                                                 while (GetLatestIndex () > last_notification_index) {
162                                                         try {
163                                                                 CoreEventLog.OnEntryWritten (GetEntry (last_notification_index++));
164                                                         } catch (Exception ex) {
165                                                                 // FIXME: find some proper way to output this error
166                                                                 Debug.WriteLine (ex);
167                                                         }
168                                                 }
169                                         } finally {
170                                                 lock (this)
171                                                         _notifying = false;
172                                         }
173                                 };
174                         }
175                         last_notification_index = GetLatestIndex ();
176                         file_watcher.EnableRaisingEvents = true;
177                 }
178
179                 public override void EndInit () { }
180
181                 public override bool Exists (string logName, string machineName)
182                 {
183                         string logDir = FindLogStore (logName);
184                         return Directory.Exists (logDir);
185                 }
186
187                 [MonoTODO ("Use MessageTable from PE for lookup")]
188                 protected override string FormatMessage (string source, uint eventID, string [] replacementStrings)
189                 {
190                         return string.Join (", ", replacementStrings);
191                 }
192
193                 protected override int GetEntryCount ()
194                 {
195                         string logDir = FindLogStore (CoreEventLog.Log);
196                         if (!Directory.Exists (logDir))
197                                 return 0;
198
199                         string[] logFiles = Directory.GetFiles (logDir, "*.log");
200                         return logFiles.Length;
201                 }
202
203                 protected override EventLogEntry GetEntry (int index)
204                 {
205                         string logDir = FindLogStore (CoreEventLog.Log);
206
207                         // our file names are one-based
208                         string file = Path.Combine (logDir, (index + 1).ToString (
209                                 CultureInfo.InvariantCulture) + ".log");
210
211                         using (TextReader tr = File.OpenText (file)) {
212                                 int eventIndex = int.Parse (Path.GetFileNameWithoutExtension (file),
213                                         CultureInfo.InvariantCulture);
214                                 uint instanceID = uint.Parse (tr.ReadLine ().Substring (12),
215                                         CultureInfo.InvariantCulture);
216                                 EventLogEntryType type = (EventLogEntryType)
217                                         Enum.Parse (typeof (EventLogEntryType), tr.ReadLine ().Substring (11));
218                                 string source = tr.ReadLine ().Substring (8);
219                                 string category = tr.ReadLine ().Substring (10);
220                                 short categoryNumber = short.Parse(category, CultureInfo.InvariantCulture);
221                                 string categoryName = "(" + category + ")";
222                                 DateTime timeGenerated = DateTime.ParseExact (tr.ReadLine ().Substring (15),
223                                         DateFormat, CultureInfo.InvariantCulture);
224                                 DateTime timeWritten = File.GetLastWriteTime (file);
225                                 int stringNums = int.Parse (tr.ReadLine ().Substring (20));
226                                 ArrayList replacementTemp = new ArrayList ();
227                                 StringBuilder sb = new StringBuilder ();
228                                 while (replacementTemp.Count < stringNums) {
229                                         char c = (char) tr.Read ();
230                                         if (c == '\0') {
231                                                 replacementTemp.Add (sb.ToString ());
232                                                 sb.Length = 0;
233                                         } else {
234                                                 sb.Append (c);
235                                         }
236                                 }
237                                 string [] replacementStrings = new string [replacementTemp.Count];
238                                 replacementTemp.CopyTo (replacementStrings, 0);
239
240                                 string message = FormatMessage (source, instanceID, replacementStrings);
241                                 int eventID = EventLog.GetEventID (instanceID);
242
243                                 byte [] bin = Convert.FromBase64String (tr.ReadToEnd ());
244                                 return new EventLogEntry (categoryName, categoryNumber, eventIndex,
245                                         eventID, source, message, null, Environment.MachineName,
246                                         type, timeGenerated, timeWritten, bin, replacementStrings,
247                                         instanceID);
248                         }
249                 }
250
251                 [MonoTODO]
252                 protected override string GetLogDisplayName ()
253                 {
254                         return CoreEventLog.Log;
255                 }
256
257                 protected override string [] GetLogNames (string machineName)
258                 {
259                         if (!Directory.Exists (EventLogStore))
260                                 return new string [0];
261
262                         string [] logDirs = Directory.GetDirectories (EventLogStore, "*");
263                         string [] logNames = new string [logDirs.Length];
264                         for (int i = 0; i < logDirs.Length; i++)
265                                 logNames [i] = Path.GetFileName (logDirs [i]);
266                         return logNames;
267                 }
268
269                 public override string LogNameFromSourceName (string source, string machineName)
270                 {
271                         if (!Directory.Exists (EventLogStore))
272                                 return string.Empty;
273
274                         string sourceDir = FindSourceDirectory (source);
275                         if (sourceDir == null)
276                                 return string.Empty;
277                         DirectoryInfo info = new DirectoryInfo (sourceDir);
278                         return info.Parent.Name;
279                 }
280
281                 public override bool SourceExists (string source, string machineName)
282                 {
283                         if (!Directory.Exists (EventLogStore))
284                                 return false;
285                         string sourceDir = FindSourceDirectory (source);
286                         return (sourceDir != null);
287                 }
288
289                 public override void WriteEntry (string [] replacementStrings, EventLogEntryType type, uint instanceID, short category, byte [] rawData)
290                 {
291                         lock (lockObject) {
292                                 string logDir = FindLogStore (CoreEventLog.Log);
293
294                                 int index = GetLatestIndex () + 1;
295                                 string logPath = Path.Combine (logDir, index.ToString (CultureInfo.InvariantCulture) + ".log");
296                                 try {
297                                         using (TextWriter w = File.CreateText (logPath)) {
298 #if NET_2_0
299                                                 w.WriteLine ("InstanceID: {0}", instanceID.ToString (CultureInfo.InvariantCulture));
300 #else
301                                                 w.WriteLine ("InstanceID: {0}", instanceID.ToString (CultureInfo.InvariantCulture));
302 #endif
303                                                 w.WriteLine ("EntryType: {0}", (int) type);
304                                                 w.WriteLine ("Source: {0}", CoreEventLog.Source);
305                                                 w.WriteLine ("Category: {0}", category.ToString (CultureInfo.InvariantCulture));
306                                                 w.WriteLine ("TimeGenerated: {0}", DateTime.Now.ToString (
307                                                         DateFormat, CultureInfo.InvariantCulture));
308                                                 w.WriteLine ("ReplacementStrings: {0}", replacementStrings.
309                                                         Length.ToString (CultureInfo.InvariantCulture));
310                                                 StringBuilder sb = new StringBuilder ();
311                                                 for (int i = 0; i < replacementStrings.Length; i++) {
312                                                         string replacement = replacementStrings [i];
313                                                         sb.Append (replacement);
314                                                         sb.Append ('\0');
315                                                 }
316                                                 w.Write (sb.ToString ());
317                                                 w.Write (Convert.ToBase64String (rawData));
318                                         }
319                                 } catch (IOException) {
320                                         File.Delete (logPath);
321                                 }
322                         }
323                 }
324
325                 private string FindSourceDirectory (string source)
326                 {
327                         string sourceDir = null;
328
329                         string [] logDirs = Directory.GetDirectories (EventLogStore, "*");
330                         for (int i = 0; i < logDirs.Length; i++) {
331                                 string [] sourceDirs = Directory.GetDirectories (logDirs [i], "*");
332                                 for (int j = 0; j < sourceDirs.Length; j++) {
333                                         string relativeDir = Path.GetFileName (sourceDirs [j]);
334                                         // use a case-insensitive comparison
335                                         if (string.Compare (relativeDir, source, true, CultureInfo.InvariantCulture) == 0) {
336                                                 sourceDir = sourceDirs [j];
337                                                 break;
338                                         }
339                                 }
340                         }
341                         return sourceDir;
342                 }
343
344                 private bool RunningOnLinux {
345                         get {
346                                 return ((int) Environment.OSVersion.Platform == 4 ||
347 #if NET_2_0
348                                         Environment.OSVersion.Platform == PlatformID.Unix);
349 #else
350                                         (int) Environment.OSVersion.Platform == 128);
351 #endif
352                         }
353                 }
354
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);
360
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];
370                                 }
371                         }
372
373                         return Path.Combine (EventLogStore, logName);
374                 }
375
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 (RunningOnLinux) {
385                                         return "/var/lib/mono/eventlog";
386                                 } else {
387                                         return Path.Combine (Environment.GetFolderPath (
388                                                 Environment.SpecialFolder.CommonApplicationData),
389                                                 "mono\\eventlog");
390                                 }
391                         }
392                 }
393
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 {
406                                 }
407                         }
408                         return maxIndex;
409                 }
410
411                 private static void ModifyAccessPermissions (string path, string permissions)
412                 {
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);
419
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);
425                         }
426
427                         p.WaitForExit ();
428                         if (p.ExitCode != 0) {
429                                 p.Close ();
430                                 throw new SecurityException ("Access permissions could not be modified.");
431                         }
432                         p.Close ();
433                 }
434
435 #if NET_2_0
436                 public override OverflowAction OverflowAction {
437                         get { return OverflowAction.DoNotOverwrite; }
438                 }
439
440                 public override int MinimumRetentionDays {
441                         get { return int.MaxValue; }
442                 }
443
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"); }
447                 }
448
449                 public override void ModifyOverflowPolicy (OverflowAction action, int retentionDays)
450                 {
451                         throw new NotSupportedException ("This EventLog implementation does not support modifying overflow policy");
452                 }
453
454                 public override void RegisterDisplayName (string resourceFile, long resourceId)
455                 {
456                         throw new NotSupportedException ("This EventLog implementation does not support registering display name");
457                 }
458 #endif
459         }
460 }