LocalFileEventLog.cs 15 KB

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