Skip to content

Commit da9d3fc

Browse files
committed
Add ability to send posix/ansi signals
1 parent 25a931c commit da9d3fc

File tree

2 files changed

+141
-0
lines changed

2 files changed

+141
-0
lines changed

src/Renci.SshNet/CommandSignal.cs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
namespace Renci.SshNet
2+
{
3+
/// <summary>
4+
/// The ssh compatible POSIX/ANSI signals with their libc compatible values.
5+
/// </summary>
6+
#pragma warning disable CA1720 // Identifier contains type name
7+
public enum CommandSignal
8+
{
9+
/// <summary>
10+
/// Hangup (POSIX).
11+
/// </summary>
12+
HUP = 1,
13+
14+
/// <summary>
15+
/// Interrupt (ANSI).
16+
/// </summary>
17+
INT = 2,
18+
19+
/// <summary>
20+
/// Quit (POSIX).
21+
/// </summary>
22+
QUIT = 3,
23+
24+
/// <summary>
25+
/// Illegal instruction (ANSI).
26+
/// </summary>
27+
ILL = 4,
28+
29+
/// <summary>
30+
/// Abort (ANSI).
31+
/// </summary>
32+
ABRT = 6,
33+
34+
/// <summary>
35+
/// Floating-point exception (ANSI).
36+
/// </summary>
37+
FPE = 8,
38+
39+
/// <summary>
40+
/// Kill, unblockable (POSIX).
41+
/// </summary>
42+
KILL = 9,
43+
44+
/// <summary>
45+
/// User-defined signal 1 (POSIX).
46+
/// </summary>
47+
USR1 = 10,
48+
49+
/// <summary>
50+
/// Segmentation violation (ANSI).
51+
/// </summary>
52+
SEGV = 11,
53+
54+
/// <summary>
55+
/// User-defined signal 2 (POSIX).
56+
/// </summary>
57+
USR2 = 12,
58+
59+
/// <summary>
60+
/// Broken pipe (POSIX).
61+
/// </summary>
62+
PIPE = 13,
63+
64+
/// <summary>
65+
/// Alarm clock (POSIX).
66+
/// </summary>
67+
ALRM = 14,
68+
69+
/// <summary>
70+
/// Termination (ANSI).
71+
/// </summary>
72+
TERM = 15,
73+
}
74+
}

src/Renci.SshNet/SshCommand.cs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,73 @@ public void CancelAsync(bool forceKill = false, int millisecondsTimeout = 500)
478478
}
479479
}
480480

481+
private static string? GetSignalName(CommandSignal signal)
482+
{
483+
#if NETCOREAPP
484+
return Enum.GetName(signal);
485+
#else
486+
487+
// Boxes signal, but Enum.GetName does not have a non-boxing overload prior to .NET Core.
488+
return Enum.GetName(typeof(CommandSignal), signal);
489+
#endif
490+
}
491+
492+
/// <summary>
493+
/// Tries to send a POSIX/ANSI signal to the remote process executing the command, such as SIGINT or SIGTERM.
494+
/// </summary>
495+
/// <param name="signal">The signal to send</param>
496+
/// <returns>If the signal was sent.</returns>
497+
public bool TrySendSignal(CommandSignal signal)
498+
{
499+
var signalName = GetSignalName(signal);
500+
if (signalName is null)
501+
{
502+
return false;
503+
}
504+
505+
if (_tcs is null || _tcs.Task.IsCompleted || _channel?.IsOpen != true)
506+
{
507+
return false;
508+
}
509+
510+
try
511+
{
512+
// Try to send the cancellation signal.
513+
return _channel.SendSignalRequest(signalName);
514+
}
515+
catch (Exception)
516+
{
517+
// Exception can be ignored since we are in a Try method
518+
// Possible exceptions here: InvalidOperationException, SshConnectionException, SshOperationTimeoutException
519+
}
520+
521+
return false;
522+
}
523+
524+
/// <summary>
525+
/// Tries to send a POSIX/ANSI signal to the remote process executing the command, such as SIGINT or SIGTERM.
526+
/// </summary>
527+
/// <param name="signal">The signal to send</param>
528+
/// <exception cref="ArgumentException">Signal was not a valid CommandSignal.</exception>
529+
/// <exception cref="SshConnectionException">The client is not connected.</exception>
530+
/// <exception cref="SshOperationTimeoutException">The operation timed out.</exception>
531+
/// <exception cref="InvalidOperationException">The size of the packet exceeds the maximum size defined by the protocol.</exception>
532+
/// <exception cref="InvalidOperationException">Command has not been started.</exception>
533+
public void SendSignal(CommandSignal signal)
534+
{
535+
var signalName = GetSignalName(signal);
536+
if (signalName is null)
537+
{
538+
throw new ArgumentException("Signal was not a valid CommandSignal.");
539+
}
540+
if (_tcs is null || _tcs.Task.IsCompleted || _channel?.IsOpen != true)
541+
{
542+
throw new InvalidOperationException("Command has not been started.");
543+
}
544+
545+
_ = _channel.SendSignalRequest(signalName);
546+
}
547+
481548
/// <summary>
482549
/// Executes the command specified by <see cref="CommandText"/>.
483550
/// </summary>

0 commit comments

Comments
 (0)