| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307 |
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Threading;
- using Renci.SshNet.Abstractions;
- using Renci.SshNet.Channels;
- using Renci.SshNet.Common;
- namespace Renci.SshNet
- {
- /// <summary>
- /// Represents instance of the SSH shell object.
- /// </summary>
- public class Shell : IDisposable
- {
- 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 ManualResetEvent _dataReaderTaskCompleted;
- private IChannelSession _channel;
- private AutoResetEvent _channelClosedWaitHandle;
- private Stream _input;
- /// <summary>
- /// Gets a value indicating whether this shell is started.
- /// </summary>
- /// <value>
- /// <see langword="true"/> if started is started; otherwise, <see langword="false"/>.
- /// </value>
- public bool IsStarted { get; private set; }
- /// <summary>
- /// Occurs when shell is starting.
- /// </summary>
- public event EventHandler<EventArgs> Starting;
- /// <summary>
- /// Occurs when shell is started.
- /// </summary>
- public event EventHandler<EventArgs> Started;
- /// <summary>
- /// Occurs when shell is stopping.
- /// </summary>
- public event EventHandler<EventArgs> Stopping;
- /// <summary>
- /// Occurs when shell is stopped.
- /// </summary>
- public event EventHandler<EventArgs> Stopped;
- /// <summary>
- /// Occurs when an error occurred.
- /// </summary>
- public event EventHandler<ExceptionEventArgs> ErrorOccurred;
- /// <summary>
- /// Initializes a new instance of the <see cref="Shell"/> class.
- /// </summary>
- /// <param name="session">The session.</param>
- /// <param name="input">The input.</param>
- /// <param name="output">The output.</param>
- /// <param name="extendedOutput">The extended output.</param>
- /// <param name="terminalName">Name of the terminal.</param>
- /// <param name="columns">The columns.</param>
- /// <param name="rows">The rows.</param>
- /// <param name="width">The width.</param>
- /// <param name="height">The height.</param>
- /// <param name="terminalModes">The terminal modes.</param>
- /// <param name="bufferSize">Size of the buffer for output stream.</param>
- 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)
- {
- _session = session;
- _input = input;
- _outputStream = output;
- _extendedOutputStream = extendedOutput;
- _terminalName = terminalName;
- _columns = columns;
- _rows = rows;
- _width = width;
- _height = height;
- _terminalModes = terminalModes;
- _bufferSize = bufferSize;
- }
- /// <summary>
- /// Starts this shell.
- /// </summary>
- /// <exception cref="SshException">Shell is started.</exception>
- public void Start()
- {
- if (IsStarted)
- {
- throw new SshException("Shell is started.");
- }
- 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();
- _ = _channel.SendPseudoTerminalRequest(_terminalName, _columns, _rows, _width, _height, _terminalModes);
- _ = _channel.SendShellRequest();
- _channelClosedWaitHandle = new AutoResetEvent(initialState: false);
- // Start input stream listener
- _dataReaderTaskCompleted = new ManualResetEvent(initialState: false);
- ThreadAbstraction.ExecuteThread(() =>
- {
- try
- {
- var buffer = new byte[_bufferSize];
- while (_channel.IsOpen)
- {
- var readTask = _input.ReadAsync(buffer, 0, buffer.Length);
- var readWaitHandle = ((IAsyncResult)readTask).AsyncWaitHandle;
- if (WaitHandle.WaitAny(new[] { readWaitHandle, _channelClosedWaitHandle }) == 0)
- {
- var read = readTask.GetAwaiter().GetResult();
- _channel.SendData(buffer, 0, read);
- continue;
- }
- break;
- }
- }
- catch (Exception exp)
- {
- RaiseError(new ExceptionEventArgs(exp));
- }
- finally
- {
- _ = _dataReaderTaskCompleted.Set();
- }
- });
- IsStarted = true;
- Started?.Invoke(this, EventArgs.Empty);
- }
- /// <summary>
- /// Stops this shell.
- /// </summary>
- /// <exception cref="SshException">Shell is not started.</exception>
- 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)
- {
- 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 (Stopping is not null)
- {
- // Handle event on different thread
- ThreadAbstraction.ExecuteThread(() => 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 (Stopped != null)
- {
- // Handle event on different thread
- ThreadAbstraction.ExecuteThread(() => Stopped(this, EventArgs.Empty));
- }
- _channel = null;
- }
- /// <summary>
- /// Unsubscribes the current <see cref="Shell"/> from session events.
- /// </summary>
- /// <param name="session">The session.</param>
- /// <remarks>
- /// Does nothing when <paramref name="session"/> is <see langword="null"/>.
- /// </remarks>
- private void UnsubscribeFromSessionEvents(ISession session)
- {
- if (session is null)
- {
- return;
- }
- session.Disconnected -= Session_Disconnected;
- session.ErrorOccured -= Session_ErrorOccured;
- }
- private bool _disposed;
- /// <summary>
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
- /// </summary>
- public void Dispose()
- {
- Dispose(disposing: true);
- GC.SuppressFinalize(this);
- }
- /// <summary>
- /// Releases unmanaged and - optionally - managed resources.
- /// </summary>
- /// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources; <see langword="false"/> to release only unmanaged resources.</param>
- protected virtual void Dispose(bool disposing)
- {
- if (_disposed)
- {
- return;
- }
- if (disposing)
- {
- UnsubscribeFromSessionEvents(_session);
- var channelClosedWaitHandle = _channelClosedWaitHandle;
- if (channelClosedWaitHandle is not null)
- {
- channelClosedWaitHandle.Dispose();
- _channelClosedWaitHandle = null;
- }
- var channel = _channel;
- if (channel is not null)
- {
- channel.Dispose();
- _channel = null;
- }
- var dataReaderTaskCompleted = _dataReaderTaskCompleted;
- if (dataReaderTaskCompleted is not null)
- {
- dataReaderTaskCompleted.Dispose();
- _dataReaderTaskCompleted = null;
- }
- _disposed = true;
- }
- }
- /// <summary>
- /// Finalizes an instance of the <see cref="Shell"/> class.
- /// </summary>
- ~Shell()
- {
- Dispose(disposing: false);
- }
- }
- }
|