SubsystemSession.cs 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. using System;
  2. using System.Globalization;
  3. using System.Text;
  4. using System.Threading;
  5. using Renci.SshNet.Channels;
  6. using Renci.SshNet.Common;
  7. namespace Renci.SshNet
  8. {
  9. /// <summary>
  10. /// Base class for SSH subsystem implementations
  11. /// </summary>
  12. internal abstract class SubsystemSession : IDisposable
  13. {
  14. private readonly ISession _session;
  15. private readonly string _subsystemName;
  16. private IChannelSession _channel;
  17. private Exception _exception;
  18. private EventWaitHandle _errorOccuredWaitHandle = new ManualResetEvent(false);
  19. private EventWaitHandle _channelClosedWaitHandle = new ManualResetEvent(false);
  20. /// <summary>
  21. /// Specifies a timeout to wait for operation to complete
  22. /// </summary>
  23. protected TimeSpan OperationTimeout { get; private set; }
  24. /// <summary>
  25. /// Occurs when an error occurred.
  26. /// </summary>
  27. public event EventHandler<ExceptionEventArgs> ErrorOccurred;
  28. /// <summary>
  29. /// Occurs when session has been disconnected form the server.
  30. /// </summary>
  31. public event EventHandler<EventArgs> Disconnected;
  32. /// <summary>
  33. /// Gets the channel associated with this session.
  34. /// </summary>
  35. /// <value>
  36. /// The channel associated with this session.
  37. /// </value>
  38. internal IChannelSession Channel
  39. {
  40. get { return _channel; }
  41. }
  42. /// <summary>
  43. /// Gets the character encoding to use.
  44. /// </summary>
  45. protected Encoding Encoding { get; private set; }
  46. /// <summary>
  47. /// Initializes a new instance of the SubsystemSession class.
  48. /// </summary>
  49. /// <param name="session">The session.</param>
  50. /// <param name="subsystemName">Name of the subsystem.</param>
  51. /// <param name="operationTimeout">The operation timeout.</param>
  52. /// <param name="encoding">The character encoding to use.</param>
  53. /// <exception cref="ArgumentNullException"><paramref name="session" /> or <paramref name="subsystemName" /> or <paramref name="encoding"/>is null.</exception>
  54. protected SubsystemSession(ISession session, string subsystemName, TimeSpan operationTimeout, Encoding encoding)
  55. {
  56. if (session == null)
  57. throw new ArgumentNullException("session");
  58. if (subsystemName == null)
  59. throw new ArgumentNullException("subsystemName");
  60. if (encoding == null)
  61. throw new ArgumentNullException("encoding");
  62. _session = session;
  63. _subsystemName = subsystemName;
  64. OperationTimeout = operationTimeout;
  65. Encoding = encoding;
  66. }
  67. /// <summary>
  68. /// Connects subsystem on SSH channel.
  69. /// </summary>
  70. public void Connect()
  71. {
  72. _channel = _session.CreateChannelSession();
  73. _session.ErrorOccured += Session_ErrorOccured;
  74. _session.Disconnected += Session_Disconnected;
  75. _channel.DataReceived += Channel_DataReceived;
  76. _channel.Closed += Channel_Closed;
  77. _channel.Open();
  78. _channel.SendSubsystemRequest(_subsystemName);
  79. OnChannelOpen();
  80. }
  81. /// <summary>
  82. /// Disconnects subsystem channel.
  83. /// </summary>
  84. public void Disconnect()
  85. {
  86. _channel.SendEof();
  87. _channel.Close();
  88. }
  89. /// <summary>
  90. /// Sends data to the subsystem.
  91. /// </summary>
  92. /// <param name="data">The data to be sent.</param>
  93. public void SendData(byte[] data)
  94. {
  95. _channel.SendData(data);
  96. }
  97. /// <summary>
  98. /// Called when channel is open.
  99. /// </summary>
  100. protected abstract void OnChannelOpen();
  101. /// <summary>
  102. /// Called when data is received.
  103. /// </summary>
  104. /// <param name="dataTypeCode">The data type code.</param>
  105. /// <param name="data">The data.</param>
  106. protected abstract void OnDataReceived(uint dataTypeCode, byte[] data);
  107. /// <summary>
  108. /// Raises the error.
  109. /// </summary>
  110. /// <param name="error">The error.</param>
  111. protected void RaiseError(Exception error)
  112. {
  113. _exception = error;
  114. var errorOccuredWaitHandle = _errorOccuredWaitHandle;
  115. if (errorOccuredWaitHandle != null)
  116. errorOccuredWaitHandle.Set();
  117. SignalErrorOccurred(error);
  118. }
  119. private void Channel_DataReceived(object sender, ChannelDataEventArgs e)
  120. {
  121. OnDataReceived(e.DataTypeCode, e.Data);
  122. }
  123. private void Channel_Closed(object sender, ChannelEventArgs e)
  124. {
  125. var channelClosedWaitHandle = _channelClosedWaitHandle;
  126. if (channelClosedWaitHandle != null)
  127. channelClosedWaitHandle.Set();
  128. }
  129. internal void WaitOnHandle(WaitHandle waitHandle, TimeSpan operationTimeout)
  130. {
  131. var waitHandles = new[]
  132. {
  133. _errorOccuredWaitHandle,
  134. _channelClosedWaitHandle,
  135. waitHandle
  136. };
  137. switch (WaitHandle.WaitAny(waitHandles, operationTimeout))
  138. {
  139. case 0:
  140. throw _exception;
  141. case 1:
  142. throw new SshException("Channel was closed.");
  143. case WaitHandle.WaitTimeout:
  144. throw new SshOperationTimeoutException(string.Format(CultureInfo.CurrentCulture, "Operation has timed out."));
  145. }
  146. }
  147. private void Session_Disconnected(object sender, EventArgs e)
  148. {
  149. SignalDisconnected();
  150. RaiseError(new SshException("Connection was lost"));
  151. }
  152. private void Session_ErrorOccured(object sender, ExceptionEventArgs e)
  153. {
  154. RaiseError(e.Exception);
  155. }
  156. private void SignalErrorOccurred(Exception error)
  157. {
  158. var errorOccurred = ErrorOccurred;
  159. if (errorOccurred != null)
  160. {
  161. errorOccurred(this, new ExceptionEventArgs(error));
  162. }
  163. }
  164. private void SignalDisconnected()
  165. {
  166. var disconnected = Disconnected;
  167. if (disconnected != null)
  168. {
  169. disconnected(this, new EventArgs());
  170. }
  171. }
  172. #region IDisposable Members
  173. private bool _isDisposed;
  174. /// <summary>
  175. /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
  176. /// </summary>
  177. public void Dispose()
  178. {
  179. Dispose(true);
  180. GC.SuppressFinalize(this);
  181. }
  182. /// <summary>
  183. /// Releases unmanaged and - optionally - managed resources
  184. /// </summary>
  185. /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
  186. protected virtual void Dispose(bool disposing)
  187. {
  188. // Check to see if Dispose has already been called.
  189. if (!_isDisposed)
  190. {
  191. if (_channel != null)
  192. {
  193. _channel.DataReceived -= Channel_DataReceived;
  194. _channel.Closed -= Channel_Closed;
  195. _channel.Dispose();
  196. _channel = null;
  197. }
  198. _session.ErrorOccured -= Session_ErrorOccured;
  199. _session.Disconnected -= Session_Disconnected;
  200. // If disposing equals true, dispose all managed
  201. // and unmanaged resources.
  202. if (disposing)
  203. {
  204. // Dispose managed resources.
  205. if (_errorOccuredWaitHandle != null)
  206. {
  207. _errorOccuredWaitHandle.Dispose();
  208. _errorOccuredWaitHandle = null;
  209. }
  210. if (_channelClosedWaitHandle != null)
  211. {
  212. _channelClosedWaitHandle.Dispose();
  213. _channelClosedWaitHandle = null;
  214. }
  215. }
  216. // Note disposing has been done.
  217. _isDisposed = true;
  218. }
  219. }
  220. /// <summary>
  221. /// Finalizes an instance of the <see cref="SubsystemSession" /> class.
  222. /// </summary>
  223. ~SubsystemSession()
  224. {
  225. // Do not re-create Dispose clean-up code here.
  226. // Calling Dispose(false) is optimal in terms of
  227. // readability and maintainability.
  228. Dispose(false);
  229. }
  230. #endregion
  231. }
  232. }