Shell
Represents instance of the SSH shell object.
using Renci.SshNet.Abstractions;
using Renci.SshNet.Channels;
using Renci.SshNet.Common;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Renci.SshNet
{
public class Shell : IDisposable
{
private const int DefaultBufferSize = 1024;
private readonly ISession _session;
private readonly string _terminalName;
private readonly uint _columns;
private readonly uint _rows;
private readonly uint _width;
private readonly uint _height;
private readonly IDictionary<TerminalModes, uint> _terminalModes;
private readonly Stream _outputStream;
private readonly Stream _extendedOutputStream;
private readonly int _bufferSize;
private readonly bool _noTerminal;
private ManualResetEvent _dataReaderTaskCompleted;
private IChannelSession _channel;
private AutoResetEvent _channelClosedWaitHandle;
private Stream _input;
private bool _disposed;
public bool IsStarted { get; set; }
public event EventHandler<EventArgs> Starting;
public event EventHandler<EventArgs> Started;
public event EventHandler<EventArgs> Stopping;
public event EventHandler<EventArgs> Stopped;
public event EventHandler<ExceptionEventArgs> ErrorOccurred;
internal Shell(ISession session, Stream input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary<TerminalModes, uint> terminalModes, int bufferSize)
: this(session, input, output, extendedOutput, bufferSize, false)
{
_terminalName = terminalName;
_columns = columns;
_rows = rows;
_width = width;
_height = height;
_terminalModes = terminalModes;
}
internal Shell(ISession session, Stream input, Stream output, Stream extendedOutput, int bufferSize)
: this(session, input, output, extendedOutput, bufferSize, true)
{
}
private Shell(ISession session, Stream input, Stream output, Stream extendedOutput, int bufferSize, bool noTerminal)
{
if (bufferSize == -1)
bufferSize = 1024;
if (bufferSize <= 0)
throw new ArgumentOutOfRangeException("bufferSize");
_session = session;
_input = input;
_outputStream = output;
_extendedOutputStream = extendedOutput;
_bufferSize = bufferSize;
_noTerminal = noTerminal;
}
public void Start()
{
if (IsStarted)
throw new SshException("Shell is started.");
this.Starting?.Invoke(this, EventArgs.Empty);
_channel = _session.CreateChannelSession();
_channel.DataReceived += Channel_DataReceived;
_channel.ExtendedDataReceived += Channel_ExtendedDataReceived;
_channel.Closed += Channel_Closed;
_session.Disconnected += Session_Disconnected;
_session.ErrorOccured += Session_ErrorOccured;
_channel.Open();
if (!_noTerminal && !_channel.SendPseudoTerminalRequest(_terminalName, _columns, _rows, _width, _height, _terminalModes))
throw new SshException("The pseudo-terminal request was not accepted by the server. Consult the server log for more information.");
if (!_channel.SendShellRequest())
throw new SshException("The request to start a shell was not accepted by the server. Consult the server log for more information.");
_channelClosedWaitHandle = new AutoResetEvent(false);
_dataReaderTaskCompleted = new ManualResetEvent(false);
ThreadAbstraction.ExecuteThread(delegate {
try {
byte[] array = new byte[_bufferSize];
while (_channel.IsOpen) {
Task<int> task = _input.ReadAsync(array, 0, array.Length);
WaitHandle asyncWaitHandle = ((IAsyncResult)task).AsyncWaitHandle;
if (WaitHandle.WaitAny(new WaitHandle[2] {
asyncWaitHandle,
_channelClosedWaitHandle
}) != 0)
break;
int result = task.GetAwaiter().GetResult();
_channel.SendData(array, 0, result);
}
} catch (Exception exception) {
RaiseError(new ExceptionEventArgs(exception));
} finally {
_dataReaderTaskCompleted.Set();
}
});
IsStarted = true;
this.Started?.Invoke(this, EventArgs.Empty);
}
public void Stop()
{
if (!IsStarted)
throw new SshException("Shell is not started.");
_channel?.Dispose();
}
private void Session_ErrorOccured(object sender, ExceptionEventArgs e)
{
RaiseError(e);
}
private void RaiseError(ExceptionEventArgs e)
{
this.ErrorOccurred?.Invoke(this, e);
}
private void Session_Disconnected(object sender, EventArgs e)
{
Stop();
}
private void Channel_ExtendedDataReceived(object sender, ChannelExtendedDataEventArgs e)
{
_extendedOutputStream?.Write(e.Data, 0, e.Data.Length);
}
private void Channel_DataReceived(object sender, ChannelDataEventArgs e)
{
_outputStream?.Write(e.Data, 0, e.Data.Length);
}
private void Channel_Closed(object sender, ChannelEventArgs e)
{
if (this.Stopping != null)
ThreadAbstraction.ExecuteThread(delegate {
this.Stopping(this, EventArgs.Empty);
});
_channel.Dispose();
_channelClosedWaitHandle.Set();
_input.Dispose();
_input = null;
_dataReaderTaskCompleted.WaitOne(_session.ConnectionInfo.Timeout);
_dataReaderTaskCompleted.Dispose();
_dataReaderTaskCompleted = null;
_channel.DataReceived -= Channel_DataReceived;
_channel.ExtendedDataReceived -= Channel_ExtendedDataReceived;
_channel.Closed -= Channel_Closed;
UnsubscribeFromSessionEvents(_session);
if (this.Stopped != null)
ThreadAbstraction.ExecuteThread(delegate {
this.Stopped(this, EventArgs.Empty);
});
_channel = null;
}
private void UnsubscribeFromSessionEvents(ISession session)
{
if (session != null) {
session.Disconnected -= Session_Disconnected;
session.ErrorOccured -= Session_ErrorOccured;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed && disposing) {
UnsubscribeFromSessionEvents(_session);
AutoResetEvent channelClosedWaitHandle = _channelClosedWaitHandle;
if (channelClosedWaitHandle != null) {
channelClosedWaitHandle.Dispose();
_channelClosedWaitHandle = null;
}
IChannelSession channel = _channel;
if (channel != null) {
channel.Dispose();
_channel = null;
}
ManualResetEvent dataReaderTaskCompleted = _dataReaderTaskCompleted;
if (dataReaderTaskCompleted != null) {
dataReaderTaskCompleted.Dispose();
_dataReaderTaskCompleted = null;
}
_disposed = true;
}
}
}
}