[bcl] Use List instead of ArrayList () in a few places.
[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.Generic;
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;
41
42 namespace System.Diagnostics
43 {
44         internal class LocalFileEventLog : EventLogImpl
45         {
46                 const string DateFormat = "yyyyMMddHHmmssfff";
47                 static readonly object lockObject = new object ();
48                 FileSystemWatcher file_watcher;
49                 int last_notification_index;
50                 bool _notifying;
51
52                 public LocalFileEventLog (EventLog coreEventLog) : base (coreEventLog)
53                 {
54                 }
55
56                 public override void BeginInit () {
57                 }
58
59                 public override void Clear ()
60                 {
61                         string logDir = FindLogStore (CoreEventLog.Log);
62                         if (!Directory.Exists (logDir))
63                                 return;
64
65                         foreach (string file in Directory.GetFiles (logDir, "*.log"))
66                                 File.Delete (file);
67                 }
68
69                 public override void Close ()
70                 {
71                         if (file_watcher != null) {
72                                 file_watcher.EnableRaisingEvents = false;
73                                 file_watcher = null; // force creation of new FileSystemWatcher
74                         }
75                 }
76
77                 public override void CreateEventSource (EventSourceCreationData sourceData)
78                 {
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);
86
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");
97                                 }
98                         }
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);
103                 }
104
105                 public override void Delete (string logName, string machineName)
106                 {
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));
113
114                         Directory.Delete (logDir, true);
115                 }
116
117                 public override void DeleteEventSource (string source, string machineName)
118                 {
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));
123
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);
130                 }
131
132                 public override void Dispose (bool disposing)
133                 {
134                         Close ();
135                 }
136
137                 public override void DisableNotification ()
138                 {
139                         if (file_watcher == null)
140                                 return;
141                         file_watcher.EnableRaisingEvents = false;
142                 }
143
144                 public override void EnableNotification ()
145                 {
146                         if (file_watcher == null) {
147                                 string logDir = FindLogStore (CoreEventLog.Log);
148                                 if (!Directory.Exists (logDir))
149                                         Directory.CreateDirectory (logDir);
150
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;
158                                         }
159
160                                         // allow for file to be finished writing
161                                         Thread.Sleep (100);
162
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);
171                                                         }
172                                                 }
173                                         } finally {
174                                                 lock (this)
175                                                         _notifying = false;
176                                         }
177                                 };
178                         }
179                         last_notification_index = GetLatestIndex ();
180                         file_watcher.EnableRaisingEvents = true;
181                 }
182
183                 public override void EndInit () { }
184
185                 public override bool Exists (string logName, string machineName)
186                 {
187                         string logDir = FindLogStore (logName);
188                         return Directory.Exists (logDir);
189                 }
190
191                 [MonoTODO ("Use MessageTable from PE for lookup")]
192                 protected override string FormatMessage (string source, uint eventID, string [] replacementStrings)
193                 {
194                         return string.Join (", ", replacementStrings);
195                 }
196
197                 protected override int GetEntryCount ()
198                 {
199                         string logDir = FindLogStore (CoreEventLog.Log);
200                         if (!Directory.Exists (logDir))
201                                 return 0;
202
203                         string[] logFiles = Directory.GetFiles (logDir, "*.log");
204                         return logFiles.Length;
205                 }
206
207                 protected override EventLogEntry GetEntry (int index)
208                 {
209                         string logDir = FindLogStore (CoreEventLog.Log);
210
211                         // our file names are one-based
212                         string file = Path.Combine (logDir, (index + 1).ToString (
213                                 CultureInfo.InvariantCulture) + ".log");
214
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                                 var replacementTemp = new List<string> ();
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);
239                                         }
240                                 }
241                                 string [] replacementStrings = replacementTemp.ToArray ();
242
243                                 string message = FormatMessage (source, instanceID, replacementStrings);
244                                 int eventID = EventLog.GetEventID (instanceID);
245
246                                 byte [] bin = Convert.FromBase64String (tr.ReadToEnd ());
247                                 return new EventLogEntry (categoryName, categoryNumber, eventIndex,
248                                         eventID, source, message, null, Environment.MachineName,
249                                         type, timeGenerated, timeWritten, bin, replacementStrings,
250                                         instanceID);
251                         }
252                 }
253
254                 [MonoTODO]
255                 protected override string GetLogDisplayName ()
256                 {
257                         return CoreEventLog.Log;
258                 }
259
260                 protected override string [] GetLogNames (string machineName)
261                 {
262                         if (!Directory.Exists (EventLogStore))
263                                 return new string [0];
264
265                         string [] logDirs = Directory.GetDirectories (EventLogStore, "*");
266                         string [] logNames = new string [logDirs.Length];
267                         for (int i = 0; i < logDirs.Length; i++)
268                                 logNames [i] = Path.GetFileName (logDirs [i]);
269                         return logNames;
270                 }
271
272                 public override string LogNameFromSourceName (string source, string machineName)
273                 {
274                         if (!Directory.Exists (EventLogStore))
275                                 return string.Empty;
276
277                         string sourceDir = FindSourceDirectory (source);
278                         if (sourceDir == null)
279                                 return string.Empty;
280                         DirectoryInfo info = new DirectoryInfo (sourceDir);
281                         return info.Parent.Name;
282                 }
283
284                 public override bool SourceExists (string source, string machineName)
285                 {
286                         if (!Directory.Exists (EventLogStore))
287                                 return false;
288                         string sourceDir = FindSourceDirectory (source);
289                         return (sourceDir != null);
290                 }
291
292                 public override void WriteEntry (string [] replacementStrings, EventLogEntryType type, uint instanceID, short category, byte [] rawData)
293                 {
294                         lock (lockObject) {
295                                 string logDir = FindLogStore (CoreEventLog.Log);
296
297                                 int index = GetLatestIndex () + 1;
298                                 string logPath = Path.Combine (logDir, index.ToString (CultureInfo.InvariantCulture) + ".log");
299                                 try {
300                                         using (TextWriter w = File.CreateText (logPath)) {
301                                                 w.WriteLine ("InstanceID: {0}", instanceID.ToString (CultureInfo.InvariantCulture));
302                                                 w.WriteLine ("EntryType: {0}", (int) type);
303                                                 w.WriteLine ("Source: {0}", CoreEventLog.Source);
304                                                 w.WriteLine ("Category: {0}", category.ToString (CultureInfo.InvariantCulture));
305                                                 w.WriteLine ("TimeGenerated: {0}", DateTime.Now.ToString (
306                                                         DateFormat, CultureInfo.InvariantCulture));
307                                                 w.WriteLine ("ReplacementStrings: {0}", replacementStrings.
308                                                         Length.ToString (CultureInfo.InvariantCulture));
309                                                 StringBuilder sb = new StringBuilder ();
310                                                 for (int i = 0; i < replacementStrings.Length; i++) {
311                                                         string replacement = replacementStrings [i];
312                                                         sb.Append (replacement);
313                                                         sb.Append ('\0');
314                                                 }
315                                                 w.Write (sb.ToString ());
316                                                 w.Write (Convert.ToBase64String (rawData));
317                                         }
318                                 } catch (IOException) {
319                                         File.Delete (logPath);
320                                 }
321                         }
322                 }
323
324                 private string FindSourceDirectory (string source)
325                 {
326                         string sourceDir = null;
327
328                         string [] logDirs = Directory.GetDirectories (EventLogStore, "*");
329                         for (int i = 0; i < logDirs.Length; i++) {
330                                 string [] sourceDirs = Directory.GetDirectories (logDirs [i], "*");
331                                 for (int j = 0; j < sourceDirs.Length; j++) {
332                                         string relativeDir = Path.GetFileName (sourceDirs [j]);
333                                         // use a case-insensitive comparison
334                                         if (string.Compare (relativeDir, source, true, CultureInfo.InvariantCulture) == 0) {
335                                                 sourceDir = sourceDirs [j];
336                                                 break;
337                                         }
338                                 }
339                         }
340                         return sourceDir;
341                 }
342
343                 private bool RunningOnUnix {
344                         get {
345                                 int p = (int) Environment.OSVersion.Platform;
346                                 return ((p == 4) || (p == 128) || (p == 6));
347                         }
348                 }
349
350                 private string FindLogStore (string logName) {
351                         // when the event log store does not yet exist, there's no need
352                         // to perform a case-insensitive lookup
353                         if (!Directory.Exists (EventLogStore))
354                                 return Path.Combine (EventLogStore, logName);
355
356                         // we'll use a case-insensitive lookup to match the MS behaviour
357                         // while still allowing the original casing of the log name to be
358                         // retained
359                         string [] logDirs = Directory.GetDirectories (EventLogStore, "*");
360                         for (int i = 0; i < logDirs.Length; i++) {
361                                 string relativeDir = Path.GetFileName (logDirs [i]);
362                                 // use a case-insensitive comparison
363                                 if (string.Compare (relativeDir, logName, true, CultureInfo.InvariantCulture) == 0) {
364                                         return logDirs [i];
365                                 }
366                         }
367
368                         return Path.Combine (EventLogStore, logName);
369                 }
370
371                 private string EventLogStore {
372                         get {
373                                 // for the local file implementation, the MONO_EVENTLOG_TYPE
374                                 // environment variable can contain the path of the event log
375                                 // store by using the following syntax: local:<path>
376                                 string eventLogType = Environment.GetEnvironmentVariable (EventLog.EVENTLOG_TYPE_VAR);
377                                 if (eventLogType != null && eventLogType.Length > EventLog.LOCAL_FILE_IMPL.Length + 1)
378                                         return eventLogType.Substring (EventLog.LOCAL_FILE_IMPL.Length + 1);
379                                 if (RunningOnUnix) {
380                                         return "/var/lib/mono/eventlog";
381                                 } else {
382                                         return Path.Combine (Environment.GetFolderPath (
383                                                 Environment.SpecialFolder.CommonApplicationData),
384                                                 "mono\\eventlog");
385                                 }
386                         }
387                 }
388
389                 private int GetLatestIndex () {
390                         // our file names are one-based
391                         int maxIndex = 0;
392                         string[] logFiles = Directory.GetFiles (FindLogStore (CoreEventLog.Log), "*.log");
393                         for (int i = 0; i < logFiles.Length; i++) {
394                                 try {
395                                         string file = logFiles[i];
396                                         int index = int.Parse (Path.GetFileNameWithoutExtension (
397                                                 file), CultureInfo.InvariantCulture);
398                                         if (index > maxIndex)
399                                                 maxIndex = index;
400                                 } catch {
401                                 }
402                         }
403                         return maxIndex;
404                 }
405
406                 private static void ModifyAccessPermissions (string path, string permissions)
407                 {
408                         ProcessStartInfo pi = new ProcessStartInfo ();
409                         pi.FileName = "chmod";
410                         pi.RedirectStandardOutput = true;
411                         pi.RedirectStandardError = true;
412                         pi.UseShellExecute = false;
413                         pi.Arguments = string.Format ("{0} \"{1}\"", permissions, path);
414
415                         Process p = null;
416                         try {
417                                 p = Process.Start (pi);
418                         } catch (Exception ex) {
419                                 throw new SecurityException ("Access permissions could not be modified.", ex);
420                         }
421
422                         p.WaitForExit ();
423                         if (p.ExitCode != 0) {
424                                 p.Close ();
425                                 throw new SecurityException ("Access permissions could not be modified.");
426                         }
427                         p.Close ();
428                 }
429
430                 public override OverflowAction OverflowAction {
431                         get { return OverflowAction.DoNotOverwrite; }
432                 }
433
434                 public override int MinimumRetentionDays {
435                         get { return int.MaxValue; }
436                 }
437
438                 public override long MaximumKilobytes {
439                         get { return long.MaxValue; }
440                         set { throw new NotSupportedException ("This EventLog implementation does not support setting max kilobytes policy"); }
441                 }
442
443                 public override void ModifyOverflowPolicy (OverflowAction action, int retentionDays)
444                 {
445                         throw new NotSupportedException ("This EventLog implementation does not support modifying overflow policy");
446                 }
447
448                 public override void RegisterDisplayName (string resourceFile, long resourceId)
449                 {
450                         throw new NotSupportedException ("This EventLog implementation does not support registering display name");
451                 }
452         }
453 }