SessionLogReader.cs 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. using System;
  2. using System.Diagnostics;
  3. using System.IO;
  4. using System.Xml;
  5. using System.Text;
  6. using System.Threading;
  7. namespace WinSCP
  8. {
  9. internal class SessionLogReader : CustomLogReader
  10. {
  11. public SessionLogReader(Session session) :
  12. base(session)
  13. {
  14. _position = 0;
  15. }
  16. public void SetTimeouted()
  17. {
  18. _timeouted = true;
  19. }
  20. public override void Dispose()
  21. {
  22. using (Session.Logger.CreateCallstack())
  23. {
  24. LogContents();
  25. Cleanup();
  26. }
  27. base.Dispose();
  28. }
  29. private void Cleanup()
  30. {
  31. if (_stream != null)
  32. {
  33. Session.Logger.WriteLine("Closing log");
  34. _stream.Dispose();
  35. _stream = null;
  36. }
  37. if (_reader != null)
  38. {
  39. ((IDisposable)_reader).Dispose();
  40. _reader = null;
  41. }
  42. }
  43. public override bool Read(LogReadFlags flags)
  44. {
  45. using (Session.Logger.CreateCallstack())
  46. {
  47. bool result;
  48. if (_timeouted)
  49. {
  50. Session.Logger.WriteLine("Not reading, session has timed out");
  51. result = false;
  52. }
  53. else
  54. {
  55. bool retry;
  56. do
  57. {
  58. result = DoRead();
  59. retry = false;
  60. if (result &&
  61. IsNonEmptyElement("failure"))
  62. {
  63. SessionRemoteException e = SessionRemoteException.ReadFailure(this);
  64. Session.RaiseFailed(e);
  65. if ((flags & LogReadFlags.ThrowFailures) == 0)
  66. {
  67. retry = true;
  68. }
  69. else
  70. {
  71. throw Session.Logger.WriteException(e);
  72. }
  73. }
  74. }
  75. while (retry);
  76. }
  77. return result;
  78. }
  79. }
  80. private bool DoRead()
  81. {
  82. int interval = 50;
  83. bool result;
  84. do
  85. {
  86. if (_reader == null)
  87. {
  88. OpenLog();
  89. }
  90. Debug.Assert(_reader != null);
  91. try
  92. {
  93. result = _reader.Read();
  94. if (result)
  95. {
  96. ++_position;
  97. Session.Logger.WriteLine("Read node {0}: {1} {2}{3}{4}",
  98. _position, _reader.NodeType, _reader.Name,
  99. (_reader.HasValue && !string.IsNullOrEmpty(_reader.Name) && !string.IsNullOrEmpty(_reader.Value) ? "=" : string.Empty),
  100. _reader.Value);
  101. Session.GotOutput();
  102. }
  103. else
  104. {
  105. Session.Logger.WriteLine("Cannot read");
  106. if (!_closed)
  107. {
  108. // this should not happen as when the log is not closed,
  109. // we should get XmlException on reaching the end
  110. _closed = true;
  111. Cleanup();
  112. }
  113. Session.CheckForTimeout();
  114. }
  115. }
  116. catch (XmlException e)
  117. {
  118. Cleanup();
  119. // check if the the root cause was session abort
  120. Session.CheckForTimeout();
  121. LogContents();
  122. string message = "Error parsing session log file";
  123. // This is possibly a race condition, as we may not have processed the event with the error yet
  124. // The ExeSessionProcess loops every 100ms
  125. Thread.Sleep(200);
  126. string s = Session.GetErrorOutputMessage();
  127. if (!string.IsNullOrEmpty(s))
  128. {
  129. message += " - " + s;
  130. }
  131. throw Session.Logger.WriteException(new SessionLocalException(Session, message, e));
  132. }
  133. if (!result && !_closed)
  134. {
  135. Session.Logger.WriteLine("Waiting for log update and dispatching events for {0}", interval);
  136. Session.DispatchEvents(interval);
  137. if (interval < 500)
  138. {
  139. interval *= 2;
  140. }
  141. }
  142. }
  143. while (!result && !_closed);
  144. if (result)
  145. {
  146. LogContents();
  147. }
  148. return result;
  149. }
  150. private void LogContents()
  151. {
  152. if (Session.Logger.Logging)
  153. {
  154. try
  155. {
  156. // alterative to File.ReadAllText with write-sharing
  157. // (note that the StreamReader disposes the Stream)
  158. using (StreamReader reader = new StreamReader(new FileStream(Session.XmlLogPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite), Encoding.UTF8))
  159. {
  160. string contents = reader.ReadToEnd();
  161. if ((_logged == null) || (_logged != contents))
  162. {
  163. Session.Logger.WriteLine("Log contents:\n{0}", contents);
  164. _logged = contents;
  165. }
  166. else
  167. {
  168. Session.Logger.WriteLine("Log contents has not changed");
  169. }
  170. }
  171. }
  172. catch (Exception e)
  173. {
  174. Session.Logger.WriteLine("Error logging log contents [{0}]", e.Message);
  175. }
  176. }
  177. }
  178. private void OpenLog()
  179. {
  180. if (_closed)
  181. {
  182. throw Session.Logger.WriteException(new InvalidOperationException("Log was closed already"));
  183. }
  184. try
  185. {
  186. Session.Logger.WriteLine("Opening log without write sharing");
  187. // First try to open file without write sharing.
  188. // This fails, if WinSCP is still writing to the log file.
  189. // This is done only as a way to detect that log file is not complete yet.
  190. _stream = new PatientFileStream(Session, Session.XmlLogPath, FileMode.Open, FileAccess.Read, FileShare.Read);
  191. _closed = true;
  192. LogContents();
  193. }
  194. catch (IOException)
  195. {
  196. Session.Logger.WriteLine("Opening log with write sharing");
  197. // If log file is still being written to, open it with write sharing
  198. _stream = new PatientFileStream(Session, Session.XmlLogPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
  199. _closed = false;
  200. }
  201. Session.Logger.WriteLine("Log opened");
  202. // Allow control characters in log
  203. var settings = new XmlReaderSettings() { CheckCharacters = false };
  204. _reader = XmlReader.Create(_stream, settings);
  205. int skip = _position;
  206. Session.Logger.WriteLine("Skipping {0} nodes", skip);
  207. while (skip > 0)
  208. {
  209. if (!_reader.Read())
  210. {
  211. throw Session.Logger.WriteException(new SessionLocalException(Session, "Read less nodes than in previous log parsing"));
  212. }
  213. --skip;
  214. }
  215. }
  216. internal override XmlReader Reader
  217. {
  218. get
  219. {
  220. if (_reader == null)
  221. {
  222. throw Session.Logger.WriteException(new SessionLocalException(Session, "Reading has not commenced yet"));
  223. }
  224. return _reader;
  225. }
  226. }
  227. private int _position;
  228. private XmlReader _reader;
  229. private PatientFileStream _stream;
  230. private bool _closed;
  231. private string _logged;
  232. private bool _timeouted;
  233. }
  234. }