Skip to content

Add functionality to communicate with remote commands over stdin #1052

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 35 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
8ea1237
Merge remote-tracking branch 'refs/remotes/origin/develop'
drieseng Oct 15, 2017
0f08b85
Prepare for 2016.1.0 RTW.
drieseng Oct 15, 2017
fc114fb
Merge remote-tracking branch 'remotes/origin/develop'
drieseng May 3, 2020
23168f4
Merge remote-tracking branch 'remotes/origin/develop'
drieseng May 3, 2020
4583da5
Remove CWLs.
drieseng May 3, 2020
4eef090
Merge branch 'develop'
drieseng May 3, 2020
cca8255
Merge remote-tracking branch 'remotes/origin/develop'
drieseng Jun 1, 2020
66e24e7
Sponsoring (#691)
drieseng Jun 7, 2020
66cf838
Prepare for 2020.0.0-beta1
drieseng Jun 1, 2020
356b22a
Merge remote-tracking branch 'remotes/origin/develop'
drieseng Jun 7, 2020
61c09ff
Merge remote-tracking branch 'remotes/origin/develop'
drieseng Jun 7, 2020
d8fbae3
Update name of Sandcastle Help File Builder environment variable.
drieseng Jun 7, 2020
e9979a6
Merge remote-tracking branch 'remotes/origin/develop'
drieseng Jun 7, 2020
32f2615
Fix path to source file.
drieseng Jun 7, 2020
bf651ca
Remove local-use file.
drieseng Jun 7, 2020
9b21a46
Merge remote-tracking branch 'remotes/origin/develop'
drieseng Jun 7, 2020
1068694
Merge remote-tracking branch 'remotes/origin/develop'
drieseng Jun 7, 2020
5942469
Merge remote-tracking branch 'remotes/origin/develop'
drieseng Jun 7, 2020
84b9281
Merge branch 'develop'
drieseng Dec 30, 2020
8737fdb
Merge branch 'develop'
drieseng Dec 31, 2020
853ec99
Prepare for 2020.0.0 release.
drieseng Dec 31, 2020
2c99132
Merge branch 'develop'
drieseng Dec 31, 2020
f480937
Merge branch 'develop'
drieseng Dec 31, 2020
a06522c
Merge branch 'develop'
drieseng Jan 24, 2021
acda143
Prepare for 2020.0.1 release
drieseng Jan 24, 2021
4cdedf6
Use cryptographically secure random number generator.
drieseng May 29, 2022
49c7729
New pipe implementation for SshCommand
hifi Jan 6, 2017
796d7b0
Small fixes
hifi Jan 7, 2017
b430a83
PipeInputStream will read as much as it can without blocking
hifi Jan 7, 2017
4572bdc
Add InputStream
mdarocha Jul 13, 2022
e38eb90
Fix build on netstandard1.3
mdarocha Jul 14, 2022
ed6ac80
Merge branch 'upstream/master' into pr
mdarocha May 26, 2023
f6f7d10
Merge branch 'develop' of github.com:sshnet/SSH.NET into upstream/master
mdarocha May 26, 2023
83adee1
Merge branch 'upstream/master' into pr
mdarocha May 26, 2023
2e6a245
Merge branch 'upstream-develop' into pr
mdarocha Jun 22, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
244 changes: 244 additions & 0 deletions src/Renci.SshNet/Common/ChannelInputStream.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
using System;
using System.IO;
using Renci.SshNet.Channels;

namespace Renci.SshNet.Common
{
/// <summary>
/// ChannelInputStream is a one direction stream intended for channel data.
/// </summary>
/// <license>
/// Copyright (c) 2016 Toni Spets ([email protected])
///
/// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
/// associated documentation files (the "Software"), to deal in the Software without restriction,
/// including without limitation the rights to use, copy, modify, merge, publish, distribute,
/// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
/// furnished to do so, subject to the following conditions:
///
/// The above copyright notice and this permission notice shall be included in all copies or
/// substantial portions of the Software.
///
/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
/// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
/// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
/// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
/// OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
/// OTHER DEALINGS IN THE SOFTWARE.
/// </license>
public class ChannelInputStream : Stream
{
#region Private members

/// <summary>
/// Channel to send data to.
/// </summary>
private readonly IChannelSession _channel;

/// <summary>
/// Total bytes passed through the stream.
/// </summary>
private long _totalPosition;

/// <summary>
/// Indicates whether the current <see cref="PipeStream"/> is disposed.
/// </summary>
private bool _isDisposed;

#endregion

internal ChannelInputStream(IChannelSession channel)
{
_channel = channel;
}

#region Stream overide methods

/// <summary>
/// When overridden in a derived class, clears all buffers for this stream and causes any buffered data to be written to the underlying device.
/// </summary>
/// <exception cref="IOException">An I/O error occurs.</exception>
/// <exception cref="ObjectDisposedException">Methods were called after the stream was closed.</exception>
/// <remarks>
/// Once flushed, any subsequent read operations no longer block until requested bytes are available. Any write operation reactivates blocking
/// reads.
/// </remarks>
public override void Flush()
{
}

/// <summary>
/// When overridden in a derived class, sets the position within the current stream.
/// </summary>
/// <returns>
/// The new position within the current stream.
/// </returns>
/// <param name="offset">A byte offset relative to the origin parameter.</param>
/// <param name="origin">A value of type <see cref="SeekOrigin"/> indicating the reference point used to obtain the new position.</param>
/// <exception cref="NotSupportedException">The stream does not support seeking, such as if the stream is constructed from a pipe or console output.</exception>
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}

/// <summary>
/// When overridden in a derived class, sets the length of the current stream.
/// </summary>
/// <param name="value">The desired length of the current stream in bytes.</param>
/// <exception cref="NotSupportedException">The stream does not support both writing and seeking, such as if the stream is constructed from a pipe or console output.</exception>
public override void SetLength(long value)
{
throw new NotSupportedException();
}

///<summary>
///When overridden in a derived class, reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read.
///</summary>
///<returns>
///The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero if the stream is closed or end of the stream has been reached.
///</returns>
///<param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the current stream.</param>
///<param name="count">The maximum number of bytes to be read from the current stream.</param>
///<param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count - 1) replaced by the bytes read from the current source.</param>
///<exception cref="ArgumentException">The sum of offset and count is larger than the buffer length.</exception>
///<exception cref="ObjectDisposedException">Methods were called after the stream was closed.</exception>
///<exception cref="NotSupportedException">The stream does not support reading.</exception>
///<exception cref="ArgumentNullException"><paramref name="buffer"/> is <c>null</c>.</exception>
///<exception cref="IOException">An I/O error occurs.</exception>
///<exception cref="ArgumentOutOfRangeException">offset or count is negative.</exception>
public override int Read(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}

///<summary>
///When overridden in a derived class, writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.
///</summary>
///<param name="offset">The zero-based byte offset in buffer at which to begin copying bytes to the current stream.</param>
///<param name="count">The number of bytes to be written to the current stream.</param>
///<param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream.</param>
///<exception cref="IOException">An I/O error occurs.</exception>
///<exception cref="NotSupportedException">The stream does not support writing.</exception>
///<exception cref="ObjectDisposedException">Methods were called after the stream was closed.</exception>
///<exception cref="ArgumentNullException"><paramref name="buffer"/> is <c>null</c>.</exception>
///<exception cref="ArgumentException">The sum of offset and count is greater than the buffer length.</exception>
///<exception cref="ArgumentOutOfRangeException">offset or count is negative.</exception>
public override void Write(byte[] buffer, int offset, int count)
{
if (buffer == null)
{
throw new ArgumentNullException("buffer");
}

if (offset + count > buffer.Length)
{
throw new ArgumentException("The sum of offset and count is greater than the buffer length.");
}

if (offset < 0 || count < 0)
{
throw new ArgumentOutOfRangeException("offset", "offset or count is negative.");
}

if (_isDisposed)
{
throw CreateObjectDisposedException();
}

if (count == 0)
{
return;
}

_channel.SendData(buffer, offset, count);

_totalPosition += count;
}

/// <summary>
/// Releases the unmanaged resources used by the Stream and optionally releases the managed resources.
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
/// <remarks>
/// Disposing a <see cref="PipeStream"/> will interrupt blocking read and write operations.
/// </remarks>
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);

if (!_isDisposed)
{
_isDisposed = true;
if (_totalPosition > 0 && _channel.IsOpen) {
_channel.SendEof();
}
}
}

///<summary>
///When overridden in a derived class, gets a value indicating whether the current stream supports reading.
///</summary>
///<returns>
///true if the stream supports reading; otherwise, false.
///</returns>
public override bool CanRead
{
get { return false; }
}

/// <summary>
/// When overridden in a derived class, gets a value indicating whether the current stream supports seeking.
/// </summary>
/// <returns>
/// <c>true</c> if the stream supports seeking; otherwise, <c>false</c>.
///</returns>
public override bool CanSeek
{
get { return false; }
}

/// <summary>
/// When overridden in a derived class, gets a value indicating whether the current stream supports writing.
/// </summary>
/// <returns>
/// <c>true</c> if the stream supports writing; otherwise, <c>false</c>.
/// </returns>
public override bool CanWrite
{
get { return true; }
}

/// <summary>
/// When overridden in a derived class, gets the length in bytes of the stream.
/// </summary>
/// <returns>
/// A long value representing the length of the stream in bytes.
/// </returns>
/// <exception cref="NotSupportedException">A class derived from Stream does not support seeking.</exception>
/// <exception cref="ObjectDisposedException">Methods were called after the stream was closed.</exception>
public override long Length
{
get { throw new NotSupportedException(); }
}

/// <summary>
/// When overridden in a derived class, gets or sets the position within the current stream.
/// </summary>
/// <returns>
/// The current position within the stream.
/// </returns>
/// <exception cref="NotSupportedException">The stream does not support seeking.</exception>
public override long Position
{
get { return _totalPosition; }
set { throw new NotSupportedException(); }
}

#endregion

private ObjectDisposedException CreateObjectDisposedException()
{
return new ObjectDisposedException(GetType().FullName);
}
}
}
143 changes: 143 additions & 0 deletions src/Renci.SshNet/Common/LinkedListQueue.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
using System;
using System.Threading;

namespace Renci.SshNet.Common
{
/// <summary>
/// Fast concurrent generic linked list queue.
/// </summary>
internal class LinkedListQueue<T> : IDisposable
{
sealed class Entry<E>
{
public E Item;
public Entry<E> Next;
}

private readonly object _lock = new object();

private Entry<T> _first;
private Entry<T> _last;

private bool _isAddingCompleted;

/// <summary>
/// Gets whether this <see cref="T:Renci.SshNet.Common.LinkedListQueue`1"/> has been marked as complete for adding and is empty.
/// </summary>
/// <value>Whether this queue has been marked as complete for adding and is empty.</value>
public bool IsCompleted
{
get { return _isAddingCompleted && _first == null && _last == null; }
}

/// <summary>
/// Gets whether this <see cref="T:Renci.SshNet.Common.LinkedListQueue`1"/> has been marked as complete for adding.
/// </summary>
/// <value>Whether this queue has been marked as complete for adding.</value>
public bool IsAddingCompleted
{
get { return _isAddingCompleted; }
set
{
lock (_lock)
{
_isAddingCompleted = value;
}
}
}

/// <summary>
/// Adds the item to <see cref="T:Renci.SshNet.Common.LinkedListQueue`1"/>.
/// </summary>
/// <param name="item">The item to be added to the queue. The value can be a null reference.</param>
public void Add(T item)
{
lock (_lock)
{
if (_isAddingCompleted)
{
return;
}

var entry = new Entry<T>()
{
Item = item
};

if (_last != null)
{
_last.Next = entry;
}

_last = entry;
_first ??= entry;

Monitor.PulseAll(_lock);
}
}

/// <summary>
/// Marks the <see cref="T:Renci.SshNet.Common.LinkedListQueue`1"/> instances as not accepting any more additions.
/// </summary>
public void CompleteAdding()
{
lock (_lock)
{
IsAddingCompleted = true;
Monitor.PulseAll(_lock);
}
}

/// <summary>
/// Tries to remove an item from the <see cref="T:Renci.SshNet.Common.LinkedListQueue`1"/>.
/// </summary>
/// <returns><c>true</c>, if an item could be removed; otherwise <c>false</c>.</returns>
/// <param name="item">The item to be removed from the queue.</param>
/// <param name="wait">Wait for data or fail immediately if empty.</param>
public bool TryTake(out T item, bool wait)
{
lock (_lock)
{
if (_first == null && !wait)
{
item = default;
return false;
}

while (_first == null && !_isAddingCompleted)
{
_ = Monitor.Wait(_lock);
}

if (_first == null && _isAddingCompleted)
{
item = default;
return false;
}

item = _first.Item;
_first = _first.Next;
return true;
}
}

/// <summary>
/// Releases all resource used by the <see cref="T:Renci.SshNet.Common.LinkedListQueue`1"/> object.
/// </summary>
/// <remarks>Call <see cref="Dispose"/> when you are finished using the
/// <see cref="T:Renci.SshNet.Common.LinkedListQueue`1"/>. The <see cref="Dispose"/> method leaves the
/// <see cref="T:Renci.SshNet.Common.LinkedListQueue`1"/> in an unusable state. After calling
/// <see cref="Dispose"/>, you must release all references to the
/// <see cref="T:Renci.SshNet.Common.LinkedListQueue`1"/> so the garbage collector can reclaim the memory that
/// the <see cref="T:Renci.SshNet.Common.LinkedListQueue`1"/> was occupying.</remarks>
public void Dispose()
{
lock (_lock)
{
_first = null;
_last = null;
_isAddingCompleted = true;
}
}
}
}
Loading