f334a884f5a28ce5bda4fbc3d997ff28f8e7fbb8
[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
48                 public LocalFileEventLog (EventLog coreEventLog) : base (coreEventLog)
49                 {
50                 }
51
52                 public override void BeginInit () {
53                 }
54
55                 public override void Clear ()
56                 {
57                         string logDir = FindLogStore (CoreEventLog.Log);
58                         if (!Directory.Exists (logDir))
59                                 return;
60
61                         foreach (string file in Directory.GetFiles (logDir, "*.log"))
62                                 File.Delete (file);
63                 }
64
65                 public override void Close ()
66                 {
67                         // we don't hold any unmanaged resources
68                 }
69
70                 public override void CreateEventSource (EventSourceCreationData sourceData)
71                 {
72                         // construct path for storing log entries
73                         string logDir = FindLogStore (sourceData.LogName);
74                         // create event log store (if necessary), and modify access
75                         // permissions (unix only)
76                         if (!Directory.Exists (logDir)) {
77                                 // ensure the log name is valid for customer logs
78                                 ValidateCustomerLogName (sourceData.LogName, sourceData.MachineName);
79
80                                 Directory.CreateDirectory (logDir);
81                                 // MS does not allow an event source to be named after an already
82                                 // existing event log. To speed up checking whether a given event
83                                 // source already exists (either as a event source or event log)
84                                 // we create an event source directory named after the event log.
85                                 // This matches what MS does with the registry-based registration.
86                                 Directory.CreateDirectory (Path.Combine (logDir, sourceData.LogName));
87                                 if (RunningOnLinux) {
88                                         ModifyAccessPermissions (logDir, "777");
89                                         ModifyAccessPermissions (logDir, "+t");
90                                 }
91                         }
92                         // create directory for event source, so we can check if the event
93                         // source already exists
94                         string sourceDir = Path.Combine (logDir, sourceData.Source);
95                         Directory.CreateDirectory (sourceDir);
96                 }
97
98                 public override void Delete (string logName, string machineName)
99                 {
100                         string logDir = FindLogStore (logName);
101                         if (!Directory.Exists (logDir))
102                                 throw new InvalidOperationException (string.Format (
103                                         CultureInfo.InvariantCulture, "Event Log '{0}'"
104                                         + " does not exist on computer '{1}'.", logName,
105                                         machineName));
106
107                         Directory.Delete (logDir, true);
108                 }
109
110                 public override void DeleteEventSource (string source, string machineName)
111                 {
112                         if (!Directory.Exists (EventLogStore))
113                                 throw new ArgumentException (string.Format (
114                                         CultureInfo.InvariantCulture, "The source '{0}' is not"
115                                         + " registered on computer '{1}'.", source, machineName));
116
117                         string sourceDir = FindSourceDirectory (source);
118                         if (sourceDir == null)
119                                 throw new ArgumentException (string.Format (
120                                         CultureInfo.InvariantCulture, "The source '{0}' is not"
121                                         + " registered on computer '{1}'.", source, machineName));
122                         Directory.Delete (sourceDir);
123                 }
124
125                 public override void Dispose (bool disposing)
126                 {
127                         Close ();
128                 }
129
130                 public override void EndInit () { }
131
132                 public override bool Exists (string logName, string machineName)
133                 {
134                         string logDir = FindLogStore (logName);
135                         return Directory.Exists (logDir);
136                 }
137
138                 [MonoTODO ("Use MessageTable from PE for lookup")]
139                 protected override string FormatMessage (string source, uint eventID, string [] replacementStrings)
140                 {
141                         return string.Join (", ", replacementStrings);
142                 }
143
144                 protected override int GetEntryCount ()
145                 {
146                         string logDir = FindLogStore (CoreEventLog.Log);
147                         if (!Directory.Exists (logDir))
148                                 return 0;
149
150                         string[] logFiles = Directory.GetFiles (logDir, "*.log");
151                         return logFiles.Length;
152                 }
153
154                 protected override EventLogEntry GetEntry (int index)
155                 {
156                         string logDir = FindLogStore (CoreEventLog.Log);
157
158                         // our file names are one-based
159                         string file = Path.Combine (logDir, (index + 1).ToString (
160                                 CultureInfo.InvariantCulture) + ".log");
161
162                         using (TextReader tr = File.OpenText (file)) {
163                                 int eventIndex = int.Parse (Path.GetFileNameWithoutExtension (file),
164                                         CultureInfo.InvariantCulture);
165                                 uint instanceID = uint.Parse (tr.ReadLine ().Substring (12),
166                                         CultureInfo.InvariantCulture);
167                                 EventLogEntryType type = (EventLogEntryType)
168                                         Enum.Parse (typeof (EventLogEntryType), tr.ReadLine ().Substring (11));
169                                 string source = tr.ReadLine ().Substring (8);
170                                 string category = tr.ReadLine ().Substring (10);
171                                 short categoryNumber = short.Parse(category, CultureInfo.InvariantCulture);
172                                 string categoryName = "(" + category + ")";
173                                 DateTime timeGenerated = DateTime.ParseExact (tr.ReadLine ().Substring (15),
174                                         DateFormat, CultureInfo.InvariantCulture);
175                                 DateTime timeWritten = File.GetLastWriteTime (file);
176                                 int stringNums = int.Parse (tr.ReadLine ().Substring (20));
177                                 ArrayList replacementTemp = new ArrayList ();
178                                 StringBuilder sb = new StringBuilder ();
179                                 while (replacementTemp.Count < stringNums) {
180                                         char c = (char) tr.Read ();
181                                         if (c == '\0') {
182                                                 replacementTemp.Add (sb.ToString ());
183                                                 sb.Length = 0;
184                                         } else {
185                                                 sb.Append (c);
186                                         }
187                                 }
188                                 string [] replacementStrings = new string [replacementTemp.Count];
189                                 replacementTemp.CopyTo (replacementStrings, 0);
190
191                                 string message = FormatMessage (source, instanceID, replacementStrings);
192                                 int eventID = EventLog.GetEventID (instanceID);
193
194                                 byte [] bin = Convert.FromBase64String (tr.ReadToEnd ());
195                                 return new EventLogEntry (categoryName, categoryNumber, eventIndex,
196                                         eventID, source, message, null, Environment.MachineName,
197                                         type, timeGenerated, timeWritten, bin, replacementStrings,
198                                         instanceID);
199                         }
200                 }
201
202                 [MonoTODO]
203                 protected override string GetLogDisplayName ()
204                 {
205                         return CoreEventLog.Log;
206                 }
207
208                 protected override string [] GetLogNames (string machineName)
209                 {
210                         if (!Directory.Exists (EventLogStore))
211                                 return new string [0];
212
213                         string [] logDirs = Directory.GetDirectories (EventLogStore, "*");
214                         string [] logNames = new string [logDirs.Length];
215                         for (int i = 0; i < logDirs.Length; i++)
216                                 logNames [i] = Path.GetFileName (logDirs [i]);
217                         return logNames;
218                 }
219
220                 public override string LogNameFromSourceName (string source, string machineName)
221                 {
222                         if (!Directory.Exists (EventLogStore))
223                                 return string.Empty;
224
225                         string sourceDir = FindSourceDirectory (source);
226                         if (sourceDir == null)
227                                 return string.Empty;
228                         DirectoryInfo info = new DirectoryInfo (sourceDir);
229                         return info.Parent.Name;
230                 }
231
232                 public override bool SourceExists (string source, string machineName)
233                 {
234                         if (!Directory.Exists (EventLogStore))
235                                 return false;
236                         string sourceDir = FindSourceDirectory (source);
237                         return (sourceDir != null);
238                 }
239
240                 public override void WriteEntry (string [] replacementStrings, EventLogEntryType type, uint instanceID, short category, byte [] rawData)
241                 {
242                         lock (lockObject) {
243                                 string logDir = FindLogStore (CoreEventLog.Log);
244
245                                 int index = GetNewIndex ();
246                                 string logPath = Path.Combine (logDir, index.ToString (CultureInfo.InvariantCulture) + ".log");
247                                 try {
248                                         using (TextWriter w = File.CreateText (logPath)) {
249 #if NET_2_0
250                                                 w.WriteLine ("InstanceID: {0}", instanceID.ToString (CultureInfo.InvariantCulture));
251 #else
252                                                 w.WriteLine ("InstanceID: {0}", instanceID.ToString (CultureInfo.InvariantCulture));
253 #endif
254                                                 w.WriteLine ("EntryType: {0}", (int) type);
255                                                 w.WriteLine ("Source: {0}", CoreEventLog.Source);
256                                                 w.WriteLine ("Category: {0}", category.ToString (CultureInfo.InvariantCulture));
257                                                 w.WriteLine ("TimeGenerated: {0}", DateTime.Now.ToString (
258                                                         DateFormat, CultureInfo.InvariantCulture));
259                                                 w.WriteLine ("ReplacementStrings: {0}", replacementStrings.
260                                                         Length.ToString (CultureInfo.InvariantCulture));
261                                                 StringBuilder sb = new StringBuilder ();
262                                                 for (int i = 0; i < replacementStrings.Length; i++) {
263                                                         string replacement = replacementStrings [i];
264                                                         sb.Append (replacement);
265                                                         sb.Append ('\0');
266                                                 }
267                                                 w.Write (sb.ToString ());
268                                                 w.Write (Convert.ToBase64String (rawData));
269                                         }
270                                 } catch (IOException) {
271                                         File.Delete (logPath);
272                                 }
273                         }
274                 }
275
276                 private string FindSourceDirectory (string source)
277                 {
278                         string sourceDir = null;
279
280                         string [] logDirs = Directory.GetDirectories (EventLogStore, "*");
281                         for (int i = 0; i < logDirs.Length; i++) {
282                                 string [] sourceDirs = Directory.GetDirectories (logDirs [i], "*");
283                                 for (int j = 0; j < sourceDirs.Length; j++) {
284                                         string relativeDir = Path.GetFileName (sourceDirs [j]);
285                                         // use a case-insensitive comparison
286                                         if (string.Compare (relativeDir, source, true, CultureInfo.InvariantCulture) == 0) {
287                                                 sourceDir = sourceDirs [j];
288                                                 break;
289                                         }
290                                 }
291                         }
292                         return sourceDir;
293                 }
294
295                 private bool RunningOnLinux {
296                         get {
297                                 return ((int) Environment.OSVersion.Platform == 4 ||
298 #if NET_2_0
299                                         Environment.OSVersion.Platform == PlatformID.Unix);
300 #else
301                                         (int) Environment.OSVersion.Platform == 128);
302 #endif
303                         }
304                 }
305
306                 private string FindLogStore (string logName) {
307                         // when the event log store does not yet exist, there's no need
308                         // to perform a case-insensitive lookup
309                         if (!Directory.Exists (EventLogStore))
310                                 return Path.Combine (EventLogStore, logName);
311
312                         // we'll use a case-insensitive lookup to match the MS behaviour
313                         // while still allowing the original casing of the log name to be
314                         // retained
315                         string [] logDirs = Directory.GetDirectories (EventLogStore, "*");
316                         for (int i = 0; i < logDirs.Length; i++) {
317                                 string relativeDir = Path.GetFileName (logDirs [i]);
318                                 // use a case-insensitive comparison
319                                 if (string.Compare (relativeDir, logName, true, CultureInfo.InvariantCulture) == 0) {
320                                         return logDirs [i];
321                                 }
322                         }
323
324                         return Path.Combine (EventLogStore, logName);
325                 }
326
327                 private string EventLogStore {
328                         get {
329                                 // for the local file implementation, the MONO_EVENTLOG_TYPE
330                                 // environment variable can contain the path of the event log
331                                 // store by using the following syntax: local:<path>
332                                 string eventLogType = Environment.GetEnvironmentVariable (EventLog.EVENTLOG_TYPE_VAR);
333                                 if (eventLogType != null && eventLogType.Length > EventLog.LOCAL_FILE_IMPL.Length + 1)
334                                         return eventLogType.Substring (EventLog.LOCAL_FILE_IMPL.Length + 1);
335                                 if (RunningOnLinux) {
336                                         return "/var/lib/mono/eventlog";
337                                 } else {
338                                         return Path.Combine (Environment.GetFolderPath (
339                                                 Environment.SpecialFolder.CommonApplicationData),
340                                                 "mono/eventlog");
341                                 }
342                         }
343                 }
344
345                 private int GetNewIndex () {
346                         // our file names are one-based
347                         int maxIndex = 0;
348                         string[] logFiles = Directory.GetFiles (FindLogStore (CoreEventLog.Log), "*.log");
349                         for (int i = 0; i < logFiles.Length; i++) {
350                                 try {
351                                         string file = logFiles[i];
352                                         int index = int.Parse (Path.GetFileNameWithoutExtension (
353                                                 file), CultureInfo.InvariantCulture);
354                                         if (index > maxIndex)
355                                                 maxIndex = index;
356                                 } catch {
357                                 }
358                         }
359                         return ++maxIndex;
360                 }
361
362                 private static void ModifyAccessPermissions (string path, string permissions)
363                 {
364                         ProcessStartInfo pi = new ProcessStartInfo ();
365                         pi.FileName = "chmod";
366                         pi.RedirectStandardOutput = true;
367                         pi.RedirectStandardError = true;
368                         pi.UseShellExecute = false;
369                         pi.Arguments = string.Format ("{0} \"{1}\"", permissions, path);
370
371                         Process p = null;
372                         try {
373                                 p = Process.Start (pi);
374                         } catch (Exception ex) {
375                                 throw new SecurityException ("Access permissions could not be modified.", ex);
376                         }
377
378                         p.WaitForExit ();
379                         if (p.ExitCode != 0) {
380                                 p.Close ();
381                                 throw new SecurityException ("Access permissions could not be modified.");
382                         }
383                         p.Close ();
384                 }
385         }
386 }