Skip to content

Commit 66b2b52

Browse files
authored
RemoteProcess.StandardInputStream/StandardOutputStream: only wrap SshExceptions in IOException. (#441)
1 parent 0108e30 commit 66b2b52

File tree

10 files changed

+77
-101
lines changed

10 files changed

+77
-101
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# Tmds.Ssh
44

5-
`Tmds.Ssh` is a modern, managed .NET SSH client library for .NET 6+.
5+
Tmds.Ssh is a modern, managed .NET SSH client library for .NET 6+.
66

77
Documentation: https://tmds.github.io/Tmds.Ssh
88

src/Tmds.Ssh/ISshChannel.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,15 @@ interface ISshChannel
1313
void Abort(Exception exception);
1414

1515
ValueTask<(ChannelReadType ReadType, int BytesRead)> ReadAsync
16-
(Memory<byte>? stdoutBuffer = default,
17-
Memory<byte>? stderrBuffer = default,
18-
CancellationToken cancellationToken = default);
16+
(Memory<byte>? stdoutBuffer,
17+
Memory<byte>? stderrBuffer,
18+
CancellationToken cancellationToken,
19+
bool forStream = false);
1920

20-
ValueTask WriteAsync(ReadOnlyMemory<byte> data, CancellationToken cancellationToken = default);
21-
void WriteEof(bool noThrow = false);
21+
ValueTask WriteAsync(ReadOnlyMemory<byte> data, CancellationToken cancellationToken, bool forStream = false);
22+
void WriteEof(bool noThrow, bool forStream);
2223
bool ChangeTerminalSize(int width, int height);
2324
bool SendSignal(string signalName);
2425

25-
Exception CreateCloseException();
26+
SshException CreateCloseException();
2627
}

src/Tmds.Ssh/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
`Tmds.Ssh` is a modern, open-source SSH client library.
1+
Tmds.Ssh is a modern, open-source SSH client library.
22

33
Documentation: https://tmds.github.io/Tmds.Ssh
44
Release notes: https://github.com/tmds/Tmds.Ssh/releases

src/Tmds.Ssh/RemoteProcess.cs

Lines changed: 29 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -311,9 +311,9 @@ internal enum ReadMode
311311

312312
internal bool EofSent => _channel.EofSent;
313313

314-
private void WriteEof(bool noThrow)
314+
private void WriteEof(bool noThrow, bool forStream = false)
315315
{
316-
_channel.WriteEof(noThrow);
316+
_channel.WriteEof(noThrow, forStream);
317317
}
318318

319319
/// <summary>
@@ -355,9 +355,12 @@ public bool SendSignal(string signalName)
355355
/// <param name="buffer">The buffer to write.</param>
356356
/// <param name="cancellationToken">Token to cancel the operation.</param>
357357
public ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
358+
=> WriteAsync(buffer, cancellationToken, forStream: false);
359+
360+
internal ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken, bool forStream)
358361
{
359362
ThrowIfDisposed();
360-
return _channel.WriteAsync(buffer, cancellationToken);
363+
return _channel.WriteAsync(buffer, cancellationToken, forStream);
361364
}
362365

363366
/// <summary>
@@ -510,9 +513,11 @@ public StreamReader ReadAsStreamReader(StderrHandler stderrHandler, int bufferSi
510513
{
511514
CheckReadMode(readMode);
512515

516+
bool forStream = readMode == ReadMode.ReadStream;
517+
513518
while (true)
514519
{
515-
(ChannelReadType ReadType, int BytesRead) = await _channel.ReadAsync(stdoutBuffer, stderrBuffer, cancellationToken).ConfigureAwait(false); ;
520+
(ChannelReadType ReadType, int BytesRead) = await _channel.ReadAsync(stdoutBuffer, stderrBuffer, cancellationToken, forStream).ConfigureAwait(false); ;
516521
switch (ReadType)
517522
{
518523
case ChannelReadType.StandardOutput:
@@ -993,42 +998,31 @@ public override async ValueTask<int> ReadAsync(Memory<byte> buffer, Cancellation
993998
return 0;
994999
}
9951000

996-
try
1001+
while (true)
9971002
{
998-
while (true)
999-
{
1000-
Memory<byte>? stderrBuffer = _stderrBuffer != null ? (Memory<byte>?)_stderrBuffer : default(Memory<byte>?);
1001-
(bool isError, int bytesRead) = await _process.ReadAsync(ReadMode.ReadStream, buffer, stderrBuffer, cancellationToken).ConfigureAwait(false);
1003+
Memory<byte>? stderrBuffer = _stderrBuffer != null ? (Memory<byte>?)_stderrBuffer : default(Memory<byte>?);
1004+
(bool isError, int bytesRead) = await _process.ReadAsync(ReadMode.ReadStream, buffer, stderrBuffer, cancellationToken).ConfigureAwait(false);
10021005

1003-
if (isError)
1006+
if (isError)
1007+
{
1008+
// Handle stderr data
1009+
if (_stderrHandler != null && bytesRead > 0)
10041010
{
1005-
// Handle stderr data
1006-
if (_stderrHandler != null && bytesRead > 0)
1007-
{
1008-
await _stderrHandler.HandleBufferAsync(_stderrBuffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false);
1009-
}
1010-
// Continue reading to get stdout data
1011-
continue;
1011+
await _stderrHandler.HandleBufferAsync(_stderrBuffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false);
10121012
}
1013-
else
1013+
// Continue reading to get stdout data
1014+
continue;
1015+
}
1016+
else
1017+
{
1018+
// Signal end of stream to the stderr handler.
1019+
if (_stderrHandler != null && bytesRead == 0)
10141020
{
1015-
// Signal end of stream to the stderr handler.
1016-
if (_stderrHandler != null && bytesRead == 0)
1017-
{
1018-
await _stderrHandler.HandleBufferAsync(default, cancellationToken).ConfigureAwait(false);
1019-
}
1020-
return bytesRead;
1021+
await _stderrHandler.HandleBufferAsync(default, cancellationToken).ConfigureAwait(false);
10211022
}
1023+
return bytesRead;
10221024
}
10231025
}
1024-
catch (OperationCanceledException)
1025-
{
1026-
throw;
1027-
}
1028-
catch (Exception ex)
1029-
{
1030-
throw new IOException($"Failed to read from remote process: {ex.Message}", ex);
1031-
}
10321026
}
10331027

10341028
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
@@ -1103,27 +1097,14 @@ public override Task FlushAsync(CancellationToken cancellationToken = default)
11031097
return Task.CompletedTask; // WriteAsync always flushes.
11041098
}
11051099

1106-
public async override ValueTask WriteAsync(System.ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default(CancellationToken))
1107-
{
1108-
try
1109-
{
1110-
await _process.WriteAsync(buffer, cancellationToken).ConfigureAwait(false);
1111-
}
1112-
catch (OperationCanceledException)
1113-
{
1114-
throw;
1115-
}
1116-
catch (Exception ex)
1117-
{
1118-
throw new IOException($"Failed to write to remote process: {ex.Message}", ex);
1119-
}
1120-
}
1100+
public override ValueTask WriteAsync(System.ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default(CancellationToken))
1101+
=> _process.WriteAsync(buffer, cancellationToken, forStream: true);
11211102

11221103
public override void Close()
11231104
{
11241105
// The base Stream class calls Close for implementing Dispose.
11251106
// We mustn't throw to avoid throwing on Dispose.
1126-
_process.WriteEof(noThrow: true);
1107+
_process.WriteEof(noThrow: true, forStream: true);
11271108
}
11281109
}
11291110
}

src/Tmds.Ssh/SftpChannel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1585,7 +1585,7 @@ private async Task SendPacketsAsync()
15851585
{
15861586
try
15871587
{
1588-
await _channel.WriteAsync(packet.Data).ConfigureAwait(false);
1588+
await _channel.WriteAsync(packet.Data, cancellationToken: default).ConfigureAwait(false);
15891589
}
15901590
catch
15911591
{

src/Tmds.Ssh/SshChannel.cs

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,8 @@ public CancellationToken ChannelAborted
8585
public async ValueTask<(ChannelReadType ReadType, int BytesRead)> ReadAsync
8686
(Memory<byte>? stdoutBuffer = default,
8787
Memory<byte>? stderrBuffer = default,
88-
CancellationToken cancellationToken = default)
88+
CancellationToken cancellationToken = default,
89+
bool forStream = false)
8990
{
9091
ThrowIfDisposed();
9192

@@ -128,7 +129,7 @@ public CancellationToken ChannelAborted
128129
}
129130
}
130131

131-
using Packet packet = await ReceivePacketAsync(cancellationToken);
132+
using Packet packet = await ReceivePacketAsync(cancellationToken, forStream);
132133
MessageId messageId = packet.MessageId!.Value;
133134
switch (messageId)
134135
{
@@ -182,12 +183,12 @@ string data
182183
}
183184
}
184185

185-
public void WriteEof(bool noThrow)
186+
public void WriteEof(bool noThrow = false, bool forStream = false)
186187
{
187188
if (!noThrow)
188189
{
189190
ThrowIfDisposed();
190-
ThrowIfAborted();
191+
ThrowIfAborted(forStream);
191192
ThrowIfEofSent();
192193
}
193194

@@ -255,27 +256,27 @@ static void ThrowEofSent()
255256
}
256257
}
257258

258-
private void ThrowIfAborted()
259+
private void ThrowIfAborted(bool forStream = false)
259260
{
260261
if (_abortState >= (int)AbortState.Closed)
261262
{
262-
ThrowCloseException();
263+
ThrowCloseException(forStream);
263264
}
264265
}
265266

266-
private void ThrowCloseException()
267+
private void ThrowCloseException(bool forStream = false)
267268
{
268-
throw CreateCloseException();
269+
throw CreateCloseException(forStream);
269270
}
270271

271-
public async ValueTask WriteAsync(ReadOnlyMemory<byte> memory, CancellationToken cancellationToken)
272+
public async ValueTask WriteAsync(ReadOnlyMemory<byte> memory, CancellationToken cancellationToken = default, bool forStream = false)
272273
{
273274
ThrowIfDisposed();
274275
ThrowIfEofSent();
275276

276277
while (memory.Length > 0)
277278
{
278-
ThrowIfAborted();
279+
ThrowIfAborted(forStream);
279280

280281
int sendWindow = Volatile.Read(ref _sendWindow);
281282
if (sendWindow > 0)
@@ -342,7 +343,7 @@ public void Dispose()
342343
public void Abort(Exception exception)
343344
=> Abort(AbortState.Aborted, exception);
344345

345-
public Exception CreateCloseException()
346+
public SshException CreateCloseException()
346347
=> (AbortState)_abortState switch
347348
{
348349
AbortState.ConnectionClosed => _client.CreateCloseException(),
@@ -353,6 +354,17 @@ public Exception CreateCloseException()
353354
_ => throw new IndexOutOfRangeException($"Unhandled state: {_abortState}."),
354355
};
355356

357+
private Exception CreateCloseException(bool forStream)
358+
{
359+
SshException exception = CreateCloseException();
360+
// Stream methods should throw IOException. Wrap the exception.
361+
if (forStream)
362+
{
363+
return new IOException($"SSH failure: {exception.Message}", exception);
364+
}
365+
return exception;
366+
}
367+
356368
private void Cancel()
357369
=> Abort(AbortState.Canceled);
358370

@@ -512,7 +524,7 @@ private void TrySendPacket(Packet packet, bool canSendWhenDisposed = false)
512524
}
513525
}
514526

515-
private async ValueTask<Packet> ReceivePacketAsync(CancellationToken ct)
527+
private async ValueTask<Packet> ReceivePacketAsync(CancellationToken ct, bool forStream = false)
516528
{
517529
// Allow reading while in the Closed state so we can receive the peer CLOSE message.
518530
// After that message, the channel is completed, and TryRead returns false.
@@ -532,7 +544,7 @@ await _receiveQueue.Reader.WaitToReadAsync(ct).ConfigureAwait(false) &&
532544

533545
if (!hasPacket || !_receiveQueue.Reader.TryRead(out Packet packet))
534546
{
535-
throw CreateCloseException();
547+
throw CreateCloseException(forStream);
536548
}
537549

538550
return packet;

src/Tmds.Ssh/SshDataStream.cs

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -84,48 +84,30 @@ protected override void Dispose(bool disposing)
8484
/// <inheritdoc />
8585
public override async ValueTask<int> ReadAsync(System.Memory<byte> buffer, CancellationToken cancellationToken = default(CancellationToken))
8686
{
87-
try
87+
while (true)
8888
{
89-
while (true)
89+
(ChannelReadType ReadType, int BytesRead) = await _channel.ReadAsync(buffer, default, cancellationToken, forStream: true).ConfigureAwait(false); ;
90+
switch (ReadType)
9091
{
91-
(ChannelReadType ReadType, int BytesRead) = await _channel.ReadAsync(buffer, default, cancellationToken).ConfigureAwait(false); ;
92-
switch (ReadType)
93-
{
94-
case ChannelReadType.StandardOutput:
95-
return BytesRead;
96-
case ChannelReadType.Eof:
97-
return 0;
98-
}
92+
case ChannelReadType.StandardOutput:
93+
return BytesRead;
94+
case ChannelReadType.Eof:
95+
return 0;
9996
}
10097
}
101-
catch (SshException ex)
102-
{
103-
// TODO: move IOException wrapping into SshChannel.ReadAsync
104-
throw new IOException($"Unable to transport data: {ex.Message}.", ex);
105-
}
10698
}
10799

108100
/// <summary>
109101
/// Writes end-of-file.
110102
/// </summary>
111103
public void WriteEof()
112104
{
113-
_channel.WriteEof();
105+
_channel.WriteEof(noThrow: false, forStream: true);
114106
}
115107

116108
/// <inheritdoc />
117-
public override async ValueTask WriteAsync(System.ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default(CancellationToken))
118-
{
119-
try
120-
{
121-
await _channel.WriteAsync(buffer, cancellationToken).ConfigureAwait(false);
122-
}
123-
catch (SshException ex)
124-
{
125-
// TODO: move IOException wrapping into SshChannel.WriteAsync
126-
throw new IOException($"Unable to transport data: {ex.Message}.", ex);
127-
}
128-
}
109+
public override ValueTask WriteAsync(System.ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default(CancellationToken))
110+
=> _channel.WriteAsync(buffer, cancellationToken, forStream: true);
129111

130112
/// <inheritdoc />
131113
public override Task FlushAsync(CancellationToken cancellationToken)

src/Tmds.Ssh/SshSession.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -761,7 +761,7 @@ private void ThrowNewConnectionClosedException()
761761
throw CreateCloseException();
762762
}
763763

764-
internal Exception CreateCloseException()
764+
internal SshException CreateCloseException()
765765
{
766766
if (_abortReason == null)
767767
{

src/docfx/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
`Tmds.Ssh` is a modern, open-source SSH client library for .NET.
1+
Tmds.Ssh is a modern, open-source SSH client library for .NET.
22

33
## Getting Started
44

test/Tmds.Ssh.Tests/PublicApiTest.PublicApi.DotNet.verified.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ namespace Tmds.Ssh
242242
public System.IO.Stream StandardInputStream { get; }
243243
public System.IO.StreamWriter StandardInputWriter { get; }
244244
public void Dispose() { }
245-
[System.Runtime.CompilerServices.AsyncIteratorStateMachine(typeof(Tmds.Ssh.RemoteProcess.<ReadAllLinesAsync>d__52))]
245+
[System.Runtime.CompilerServices.AsyncIteratorStateMachine(typeof(Tmds.Ssh.RemoteProcess.<ReadAllLinesAsync>d__53))]
246246
[return: System.Runtime.CompilerServices.TupleElementNames(new string[] {
247247
"isError",
248248
"line"})]

0 commit comments

Comments
 (0)