From da9d3fc42036e6fb93b3eda8400f25e19831b8c7 Mon Sep 17 00:00:00 2001 From: Levi Gillis Date: Wed, 25 Mar 2026 14:28:37 +0100 Subject: [PATCH 1/2] Add ability to send posix/ansi signals --- src/Renci.SshNet/CommandSignal.cs | 74 +++++++++++++++++++++++++++++++ src/Renci.SshNet/SshCommand.cs | 67 ++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 src/Renci.SshNet/CommandSignal.cs diff --git a/src/Renci.SshNet/CommandSignal.cs b/src/Renci.SshNet/CommandSignal.cs new file mode 100644 index 000000000..5d37c24c6 --- /dev/null +++ b/src/Renci.SshNet/CommandSignal.cs @@ -0,0 +1,74 @@ +namespace Renci.SshNet +{ + /// + /// The ssh compatible POSIX/ANSI signals with their libc compatible values. + /// +#pragma warning disable CA1720 // Identifier contains type name + public enum CommandSignal + { + /// + /// Hangup (POSIX). + /// + HUP = 1, + + /// + /// Interrupt (ANSI). + /// + INT = 2, + + /// + /// Quit (POSIX). + /// + QUIT = 3, + + /// + /// Illegal instruction (ANSI). + /// + ILL = 4, + + /// + /// Abort (ANSI). + /// + ABRT = 6, + + /// + /// Floating-point exception (ANSI). + /// + FPE = 8, + + /// + /// Kill, unblockable (POSIX). + /// + KILL = 9, + + /// + /// User-defined signal 1 (POSIX). + /// + USR1 = 10, + + /// + /// Segmentation violation (ANSI). + /// + SEGV = 11, + + /// + /// User-defined signal 2 (POSIX). + /// + USR2 = 12, + + /// + /// Broken pipe (POSIX). + /// + PIPE = 13, + + /// + /// Alarm clock (POSIX). + /// + ALRM = 14, + + /// + /// Termination (ANSI). + /// + TERM = 15, + } +} diff --git a/src/Renci.SshNet/SshCommand.cs b/src/Renci.SshNet/SshCommand.cs index ce1042244..460e6c30e 100644 --- a/src/Renci.SshNet/SshCommand.cs +++ b/src/Renci.SshNet/SshCommand.cs @@ -478,6 +478,73 @@ public void CancelAsync(bool forceKill = false, int millisecondsTimeout = 500) } } + private static string? GetSignalName(CommandSignal signal) + { +#if NETCOREAPP + return Enum.GetName(signal); +#else + + // Boxes signal, but Enum.GetName does not have a non-boxing overload prior to .NET Core. + return Enum.GetName(typeof(CommandSignal), signal); +#endif + } + + /// + /// Tries to send a POSIX/ANSI signal to the remote process executing the command, such as SIGINT or SIGTERM. + /// + /// The signal to send + /// If the signal was sent. + public bool TrySendSignal(CommandSignal signal) + { + var signalName = GetSignalName(signal); + if (signalName is null) + { + return false; + } + + if (_tcs is null || _tcs.Task.IsCompleted || _channel?.IsOpen != true) + { + return false; + } + + try + { + // Try to send the cancellation signal. + return _channel.SendSignalRequest(signalName); + } + catch (Exception) + { + // Exception can be ignored since we are in a Try method + // Possible exceptions here: InvalidOperationException, SshConnectionException, SshOperationTimeoutException + } + + return false; + } + + /// + /// Tries to send a POSIX/ANSI signal to the remote process executing the command, such as SIGINT or SIGTERM. + /// + /// The signal to send + /// Signal was not a valid CommandSignal. + /// The client is not connected. + /// The operation timed out. + /// The size of the packet exceeds the maximum size defined by the protocol. + /// Command has not been started. + public void SendSignal(CommandSignal signal) + { + var signalName = GetSignalName(signal); + if (signalName is null) + { + throw new ArgumentException("Signal was not a valid CommandSignal."); + } + if (_tcs is null || _tcs.Task.IsCompleted || _channel?.IsOpen != true) + { + throw new InvalidOperationException("Command has not been started."); + } + + _ = _channel.SendSignalRequest(signalName); + } + /// /// Executes the command specified by . /// From 45794dc53d297a2cd3b405ecda8d43856311dff5 Mon Sep 17 00:00:00 2001 From: Levi Gillis Date: Fri, 3 Apr 2026 15:33:27 +0200 Subject: [PATCH 2/2] Fix formatting and warnigns --- src/Renci.SshNet/SshCommand.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Renci.SshNet/SshCommand.cs b/src/Renci.SshNet/SshCommand.cs index 460e6c30e..48864c5b6 100644 --- a/src/Renci.SshNet/SshCommand.cs +++ b/src/Renci.SshNet/SshCommand.cs @@ -492,7 +492,7 @@ public void CancelAsync(bool forceKill = false, int millisecondsTimeout = 500) /// /// Tries to send a POSIX/ANSI signal to the remote process executing the command, such as SIGINT or SIGTERM. /// - /// The signal to send + /// The signal to send. /// If the signal was sent. public bool TrySendSignal(CommandSignal signal) { @@ -524,7 +524,7 @@ public bool TrySendSignal(CommandSignal signal) /// /// Tries to send a POSIX/ANSI signal to the remote process executing the command, such as SIGINT or SIGTERM. /// - /// The signal to send + /// The signal to send. /// Signal was not a valid CommandSignal. /// The client is not connected. /// The operation timed out. @@ -537,6 +537,7 @@ public void SendSignal(CommandSignal signal) { throw new ArgumentException("Signal was not a valid CommandSignal."); } + if (_tcs is null || _tcs.Task.IsCompleted || _channel?.IsOpen != true) { throw new InvalidOperationException("Command has not been started.");