using System; using System.IO; using System.Linq; using System.Text; using System.Threading; using Renci.SshNet.Channels; using Renci.SshNet.Common; using System.Diagnostics; using System.Collections.Generic; using System.Globalization; using Renci.SshNet.Sftp.Responses; using Renci.SshNet.Sftp.Requests; using Renci.SshNet.Messages.Connection; namespace Renci.SshNet.Sftp { /// /// Base class for SSH subsystem implementations /// public abstract class SubsystemSession : IDisposable { private Session _session; private string _subsystemName; private ChannelSession _channel; private Exception _exception; private EventWaitHandle _errorOccuredWaitHandle = new ManualResetEvent(false); private EventWaitHandle _channelClosedWaitHandle = new ManualResetEvent(false); /// /// Specifies a timeout to wait for operation to complete /// protected TimeSpan _operationTimeout; /// /// Occurs when an error occurred. /// public event EventHandler ErrorOccurred; /// /// Occurs when session has been disconnected form the server. /// public event EventHandler Disconnected; /// /// Gets the channel number. /// protected uint ChannelNumber { get; private set; } protected Encoding Encoding { get; private set; } /// /// Initializes a new instance of the SubsystemSession class. /// /// The session. /// Name of the subsystem. /// The operation timeout. /// session /// or is null. public SubsystemSession(Session session, string subsystemName, TimeSpan operationTimeout, Encoding encoding) { if (session == null) throw new ArgumentNullException("session"); if (subsystemName == null) throw new ArgumentNullException("subsystemName"); this._session = session; this._subsystemName = subsystemName; this._operationTimeout = operationTimeout; this.Encoding = encoding; } /// /// Connects subsystem on SSH channel. /// public void Connect() { this._channel = this._session.CreateChannel(); this._session.ErrorOccured += Session_ErrorOccured; this._session.Disconnected += Session_Disconnected; this._channel.DataReceived += Channel_DataReceived; this._channel.Closed += Channel_Closed; this._channel.Open(); this.ChannelNumber = this._channel.RemoteChannelNumber; this._channel.SendSubsystemRequest(_subsystemName); this.OnChannelOpen(); } /// /// Disconnects subsystem channel. /// public void Disconnect() { this._channel.SendEof(); this._channel.Close(); } /// /// Sends data to the subsystem. /// /// The data to be sent. public void SendData(byte[] data) { this._channel.SendData(data); } /// /// Called when channel is open. /// protected abstract void OnChannelOpen(); /// /// Called when data is received. /// /// The data type code. /// The data. protected abstract void OnDataReceived(uint dataTypeCode, byte[] data); /// /// Raises the error. /// /// The error. protected void RaiseError(Exception error) { this._exception = error; this._errorOccuredWaitHandle.Set(); if (this.ErrorOccurred != null) { this.ErrorOccurred(this, new ExceptionEventArgs(error)); } } private void Channel_DataReceived(object sender, Common.ChannelDataEventArgs e) { this.OnDataReceived(e.DataTypeCode, e.Data); } private void Channel_Closed(object sender, Common.ChannelEventArgs e) { this._channelClosedWaitHandle.Set(); } internal void WaitHandle(WaitHandle waitHandle, TimeSpan operationTimeout) { var waitHandles = new WaitHandle[] { this._errorOccuredWaitHandle, this._channelClosedWaitHandle, waitHandle, }; switch (EventWaitHandle.WaitAny(waitHandles, operationTimeout)) { case 0: throw this._exception; case 1: throw new SshException("Channel was closed."); case System.Threading.WaitHandle.WaitTimeout: throw new SshOperationTimeoutException(string.Format(CultureInfo.CurrentCulture, "Operation has timed out.")); default: break; } } private void Session_Disconnected(object sender, EventArgs e) { if (this.Disconnected != null) { this.Disconnected(this, new EventArgs()); } this.RaiseError(new SshException("Connection was lost")); } private void Session_ErrorOccured(object sender, ExceptionEventArgs e) { this.RaiseError(e.Exception); } #region IDisposable Members private bool _isDisposed = false; /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Releases unmanaged and - optionally - managed resources /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool disposing) { // Check to see if Dispose has already been called. if (!this._isDisposed) { if (this._channel != null) { this._channel.DataReceived -= Channel_DataReceived; this._channel.Dispose(); this._channel = null; } this._session.ErrorOccured -= Session_ErrorOccured; this._session.Disconnected -= Session_Disconnected; // If disposing equals true, dispose all managed // and unmanaged resources. if (disposing) { // Dispose managed resources. if (this._errorOccuredWaitHandle != null) { this._errorOccuredWaitHandle.Dispose(); this._errorOccuredWaitHandle = null; } if (this._channelClosedWaitHandle != null) { this._channelClosedWaitHandle.Dispose(); this._channelClosedWaitHandle = null; } } // Note disposing has been done. _isDisposed = true; } } /// /// Finalizes an instance of the class. /// ~SubsystemSession() { // Do not re-create Dispose clean-up code here. // Calling Dispose(false) is optimal in terms of // readability and maintainability. Dispose(false); } #endregion } }