SshCommand
Represents an SSH command that can be executed.
using Renci.SshNet.Channels;
using Renci.SshNet.Common;
using Renci.SshNet.Messages.Connection;
using Renci.SshNet.Messages.Transport;
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Renci.SshNet
{
[System.Runtime.CompilerServices.NullableContext(1)]
[System.Runtime.CompilerServices.Nullable(0)]
public class SshCommand : IDisposable
{
private readonly ISession _session;
private readonly Encoding _encoding;
[System.Runtime.CompilerServices.Nullable(2)]
private IChannelSession _channel;
[System.Runtime.CompilerServices.Nullable(new byte[] {
2,
1
})]
private TaskCompletionSource<object> _tcs;
[System.Runtime.CompilerServices.Nullable(2)]
private CancellationTokenSource _cts;
private CancellationTokenRegistration _tokenRegistration;
[System.Runtime.CompilerServices.Nullable(2)]
private string _stdOut;
[System.Runtime.CompilerServices.Nullable(2)]
private string _stdErr;
private bool _hasError;
private bool _isDisposed;
[System.Runtime.CompilerServices.Nullable(2)]
private ChannelInputStream _inputStream;
private TimeSpan _commandTimeout;
private CancellationToken _userToken;
private bool _cancellationRequested;
private int _exitStatus;
private volatile bool _haveExitStatus;
public string CommandText { get; set; }
public TimeSpan CommandTimeout {
get {
return _commandTimeout;
}
set {
value.EnsureValidTimeout("CommandTimeout");
_commandTimeout = value;
}
}
public int? ExitStatus {
get {
if (!_haveExitStatus)
return null;
return _exitStatus;
}
}
[System.Runtime.CompilerServices.Nullable(2)]
[field: System.Runtime.CompilerServices.Nullable(2)]
public string ExitSignal {
[System.Runtime.CompilerServices.NullableContext(2)]
get;
[System.Runtime.CompilerServices.NullableContext(2)]
private set;
}
public Stream OutputStream { get; set; }
public Stream ExtendedOutputStream { get; set; }
public string Result {
get {
if (_stdOut != null)
return _stdOut;
if (_tcs == null)
return string.Empty;
using (StreamReader streamReader = new StreamReader(OutputStream, _encoding))
return _stdOut = streamReader.ReadToEnd();
}
}
public string Error {
get {
if (_stdErr != null)
return _stdErr;
if (_tcs == null || !_hasError)
return string.Empty;
using (StreamReader streamReader = new StreamReader(ExtendedOutputStream, _encoding))
return _stdErr = streamReader.ReadToEnd();
}
}
public Stream CreateInputStream()
{
if (_channel == null)
throw new InvalidOperationException("The input stream can be used only after calling BeginExecute and before calling EndExecute.");
if (_inputStream != null)
throw new InvalidOperationException("The input stream already exists.");
_inputStream = new ChannelInputStream(_channel);
return _inputStream;
}
internal SshCommand(ISession session, string commandText, Encoding encoding)
{
ThrowHelper.ThrowIfNull(session, "session");
ThrowHelper.ThrowIfNull(commandText, "commandText");
ThrowHelper.ThrowIfNull(encoding, "encoding");
_session = session;
CommandText = commandText;
_encoding = encoding;
CommandTimeout = Timeout.InfiniteTimeSpan;
OutputStream = new PipeStream();
ExtendedOutputStream = new PipeStream();
_session.Disconnected += Session_Disconnected;
_session.ErrorOccured += Session_ErrorOccured;
}
public Task ExecuteAsync(CancellationToken cancellationToken = default(CancellationToken))
{
ThrowHelper.ThrowObjectDisposedIf(_isDisposed, this);
if (cancellationToken.IsCancellationRequested)
return Task.FromCanceled(cancellationToken);
if (_tcs != null) {
if (!_tcs.Task.IsCompleted)
throw new InvalidOperationException("Asynchronous operation is already in progress.");
OutputStream.Dispose();
ExtendedOutputStream.Dispose();
OutputStream = new PipeStream();
ExtendedOutputStream = new PipeStream();
}
_exitStatus = 0;
_haveExitStatus = false;
ExitSignal = null;
_stdOut = null;
_stdErr = null;
_hasError = false;
_tokenRegistration.Dispose();
_tokenRegistration = default(CancellationTokenRegistration);
_cts?.Dispose();
_cts = null;
_cancellationRequested = false;
_tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
_userToken = cancellationToken;
_channel = _session.CreateChannelSession();
_channel.DataReceived += Channel_DataReceived;
_channel.ExtendedDataReceived += Channel_ExtendedDataReceived;
_channel.RequestReceived += Channel_RequestReceived;
_channel.Closed += Channel_Closed;
_channel.Open();
_channel.SendExecRequest(CommandText);
if (CommandTimeout != Timeout.InfiniteTimeSpan) {
_cts = CancellationTokenSource.CreateLinkedTokenSource(new CancellationToken[1] {
cancellationToken
});
_cts.CancelAfter(CommandTimeout);
cancellationToken = _cts.Token;
}
if (cancellationToken.CanBeCanceled)
_tokenRegistration = cancellationToken.Register(delegate(object cmd) {
((SshCommand)cmd).CancelAsync(false, 500);
}, this);
return _tcs.Task;
}
public IAsyncResult BeginExecute()
{
return BeginExecute(null, null);
}
public IAsyncResult BeginExecute([System.Runtime.CompilerServices.Nullable(2)] AsyncCallback callback)
{
return BeginExecute(callback, null);
}
[System.Runtime.CompilerServices.NullableContext(2)]
[return: System.Runtime.CompilerServices.Nullable(1)]
public IAsyncResult BeginExecute(AsyncCallback callback, object state)
{
return TaskToAsyncResult.Begin(ExecuteAsync(default(CancellationToken)), callback, state);
}
public IAsyncResult BeginExecute(string commandText, [System.Runtime.CompilerServices.Nullable(2)] AsyncCallback callback, [System.Runtime.CompilerServices.Nullable(2)] object state)
{
ThrowHelper.ThrowIfNull(commandText, "commandText");
CommandText = commandText;
return BeginExecute(callback, state);
}
public string EndExecute(IAsyncResult asyncResult)
{
Task task = TaskToAsyncResult.Unwrap(asyncResult);
TaskCompletionSource<object> tcs = _tcs;
if (task != ((tcs != null) ? tcs.Task : null))
throw new ArgumentException("Argument does not correspond to the currently executing command.", "asyncResult");
task.GetAwaiter().GetResult();
return Result;
}
public void CancelAsync(bool forceKill = false, int millisecondsTimeout = 500)
{
if (_tcs == null)
throw new InvalidOperationException("Command has not been started.");
if (!_tcs.Task.IsCompleted) {
_cancellationRequested = true;
Interlocked.MemoryBarrier();
if ((_channel?.SendSignalRequest(forceKill ? "KILL" : "TERM")).HasValue) {
try {
if (_tcs.Task.Wait(millisecondsTimeout))
return;
} catch (AggregateException) {
return;
}
SetAsyncComplete(true);
}
}
}
public string Execute()
{
ExecuteAsync(default(CancellationToken)).GetAwaiter().GetResult();
return Result;
}
public string Execute(string commandText)
{
CommandText = commandText;
return Execute();
}
private void Session_Disconnected([System.Runtime.CompilerServices.Nullable(2)] object sender, EventArgs e)
{
_tcs?.TrySetException(new SshConnectionException("An established connection was aborted by the software in your host machine.", DisconnectReason.ConnectionLost));
SetAsyncComplete(false);
}
private void Session_ErrorOccured([System.Runtime.CompilerServices.Nullable(2)] object sender, ExceptionEventArgs e)
{
_tcs?.TrySetException(e.Exception);
SetAsyncComplete(false);
}
private void SetAsyncComplete(bool setResult = true)
{
Interlocked.MemoryBarrier();
if (setResult) {
if (_userToken.IsCancellationRequested)
_tcs.TrySetCanceled(_userToken);
else if (_cts?.Token.IsCancellationRequested ?? false) {
_tcs.TrySetException(new SshOperationTimeoutException(string.Format("Command '{0}' timed out. ({1}: {2}).", CommandText, "CommandTimeout", CommandTimeout)));
} else if (_cancellationRequested) {
_tcs.TrySetCanceled();
} else {
_tcs.TrySetResult(null);
}
}
UnsubscribeFromEventsAndDisposeChannel();
OutputStream.Dispose();
ExtendedOutputStream.Dispose();
}
private void Channel_Closed([System.Runtime.CompilerServices.Nullable(2)] object sender, ChannelEventArgs e)
{
SetAsyncComplete(true);
}
private void Channel_RequestReceived([System.Runtime.CompilerServices.Nullable(2)] object sender, ChannelRequestEventArgs e)
{
ExitStatusRequestInfo exitStatusRequestInfo = e.Info as ExitStatusRequestInfo;
if (exitStatusRequestInfo != null) {
_exitStatus = (int)exitStatusRequestInfo.ExitStatus;
_haveExitStatus = true;
} else {
ExitSignalRequestInfo exitSignalRequestInfo = e.Info as ExitSignalRequestInfo;
if (exitSignalRequestInfo != null)
ExitSignal = exitSignalRequestInfo.SignalName;
else if (e.Info.WantReply) {
uint? nullable = _channel?.RemoteChannelNumber;
if (nullable.HasValue) {
uint valueOrDefault = nullable.GetValueOrDefault();
ChannelFailureMessage message = new ChannelFailureMessage(valueOrDefault);
_session.SendMessage(message);
}
}
}
}
private void Channel_ExtendedDataReceived([System.Runtime.CompilerServices.Nullable(2)] object sender, ChannelExtendedDataEventArgs e)
{
ExtendedOutputStream.Write(e.Data, 0, e.Data.Length);
if (e.DataTypeCode == 1)
_hasError = true;
}
private void Channel_DataReceived([System.Runtime.CompilerServices.Nullable(2)] object sender, ChannelDataEventArgs e)
{
OutputStream.Write(e.Data, 0, e.Data.Length);
}
private void UnsubscribeFromEventsAndDisposeChannel()
{
IChannelSession channel = _channel;
if (channel != null) {
_channel = null;
channel.DataReceived -= Channel_DataReceived;
channel.ExtendedDataReceived -= Channel_ExtendedDataReceived;
channel.RequestReceived -= Channel_RequestReceived;
channel.Closed -= Channel_Closed;
channel.Dispose();
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_isDisposed && disposing) {
_session.Disconnected -= Session_Disconnected;
_session.ErrorOccured -= Session_ErrorOccured;
UnsubscribeFromEventsAndDisposeChannel();
_inputStream?.Dispose();
_inputStream = null;
OutputStream.Dispose();
ExtendedOutputStream.Dispose();
_tokenRegistration.Dispose();
_tokenRegistration = default(CancellationTokenRegistration);
_cts?.Dispose();
_cts = null;
TaskCompletionSource<object> tcs = _tcs;
if (tcs != null) {
Task<object> task = tcs.Task;
if (task != null && !task.IsCompleted)
tcs.TrySetException(new ObjectDisposedException(GetType().FullName));
}
_isDisposed = true;
}
}
}
}