ShellStream.cs 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960
  1. #nullable enable
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. using System.Globalization;
  6. using System.IO;
  7. using System.Text;
  8. using System.Text.RegularExpressions;
  9. using System.Threading;
  10. using System.Threading.Tasks;
  11. using Renci.SshNet.Abstractions;
  12. using Renci.SshNet.Channels;
  13. using Renci.SshNet.Common;
  14. namespace Renci.SshNet
  15. {
  16. /// <summary>
  17. /// Contains operation for working with SSH Shell.
  18. /// </summary>
  19. public class ShellStream : Stream
  20. {
  21. private readonly ISession _session;
  22. private readonly Encoding _encoding;
  23. private readonly IChannelSession _channel;
  24. private readonly byte[] _carriageReturnBytes;
  25. private readonly byte[] _lineFeedBytes;
  26. private readonly object _sync = new object();
  27. private readonly byte[] _writeBuffer;
  28. private int _writeLength; // The length of the data in _writeBuffer.
  29. private byte[] _readBuffer;
  30. private int _readHead; // The index from which the data starts in _readBuffer.
  31. private int _readTail; // The index at which to add new data into _readBuffer.
  32. private bool _disposed;
  33. /// <summary>
  34. /// Occurs when data was received.
  35. /// </summary>
  36. public event EventHandler<ShellDataEventArgs>? DataReceived;
  37. /// <summary>
  38. /// Occurs when an error occurred.
  39. /// </summary>
  40. public event EventHandler<ExceptionEventArgs>? ErrorOccurred;
  41. /// <summary>
  42. /// Occurs when the channel was closed.
  43. /// </summary>
  44. public event EventHandler<EventArgs>? Closed;
  45. /// <summary>
  46. /// Gets a value indicating whether data is available on the <see cref="ShellStream"/> to be read.
  47. /// </summary>
  48. /// <value>
  49. /// <see langword="true"/> if data is available to be read; otherwise, <see langword="false"/>.
  50. /// </value>
  51. public bool DataAvailable
  52. {
  53. get
  54. {
  55. lock (_sync)
  56. {
  57. AssertValid();
  58. return _readTail != _readHead;
  59. }
  60. }
  61. }
  62. [Conditional("DEBUG")]
  63. private void AssertValid()
  64. {
  65. Debug.Assert(Monitor.IsEntered(_sync), $"Should be in lock on {nameof(_sync)}");
  66. Debug.Assert(_readHead >= 0, $"{nameof(_readHead)} should be non-negative but is {_readHead.ToString(CultureInfo.InvariantCulture)}");
  67. Debug.Assert(_readTail >= 0, $"{nameof(_readTail)} should be non-negative but is {_readTail.ToString(CultureInfo.InvariantCulture)}");
  68. Debug.Assert(_readHead <= _readBuffer.Length, $"{nameof(_readHead)} should be <= {nameof(_readBuffer)}.Length but is {_readHead.ToString(CultureInfo.InvariantCulture)}");
  69. Debug.Assert(_readTail <= _readBuffer.Length, $"{nameof(_readTail)} should be <= {nameof(_readBuffer)}.Length but is {_readTail.ToString(CultureInfo.InvariantCulture)}");
  70. Debug.Assert(_readHead <= _readTail, $"Should have {nameof(_readHead)} <= {nameof(_readTail)} but have {_readHead.ToString(CultureInfo.InvariantCulture)} <= {_readTail.ToString(CultureInfo.InvariantCulture)}");
  71. }
  72. /// <summary>
  73. /// Initializes a new instance of the <see cref="ShellStream"/> class.
  74. /// </summary>
  75. /// <param name="session">The SSH session.</param>
  76. /// <param name="terminalName">The <c>TERM</c> environment variable.</param>
  77. /// <param name="columns">The terminal width in columns.</param>
  78. /// <param name="rows">The terminal width in rows.</param>
  79. /// <param name="width">The terminal width in pixels.</param>
  80. /// <param name="height">The terminal height in pixels.</param>
  81. /// <param name="terminalModeValues">The terminal mode values.</param>
  82. /// <param name="bufferSize">The size of the buffer.</param>
  83. /// <exception cref="SshException">The channel could not be opened.</exception>
  84. /// <exception cref="SshException">The pseudo-terminal request was not accepted by the server.</exception>
  85. /// <exception cref="SshException">The request to start a shell was not accepted by the server.</exception>
  86. internal ShellStream(ISession session, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary<TerminalModes, uint> terminalModeValues, int bufferSize)
  87. {
  88. #if NET8_0_OR_GREATER
  89. ArgumentOutOfRangeException.ThrowIfNegativeOrZero(bufferSize);
  90. #else
  91. if (bufferSize <= 0)
  92. {
  93. throw new ArgumentOutOfRangeException(nameof(bufferSize));
  94. }
  95. #endif
  96. _encoding = session.ConnectionInfo.Encoding;
  97. _session = session;
  98. _carriageReturnBytes = _encoding.GetBytes("\r");
  99. _lineFeedBytes = _encoding.GetBytes("\n");
  100. _channel = _session.CreateChannelSession();
  101. _channel.DataReceived += Channel_DataReceived;
  102. _channel.Closed += Channel_Closed;
  103. _session.Disconnected += Session_Disconnected;
  104. _session.ErrorOccured += Session_ErrorOccured;
  105. _readBuffer = new byte[bufferSize];
  106. _writeBuffer = new byte[bufferSize];
  107. try
  108. {
  109. _channel.Open();
  110. if (!_channel.SendPseudoTerminalRequest(terminalName, columns, rows, width, height, terminalModeValues))
  111. {
  112. throw new SshException("The pseudo-terminal request was not accepted by the server. Consult the server log for more information.");
  113. }
  114. if (!_channel.SendShellRequest())
  115. {
  116. throw new SshException("The request to start a shell was not accepted by the server. Consult the server log for more information.");
  117. }
  118. }
  119. catch
  120. {
  121. Dispose();
  122. throw;
  123. }
  124. }
  125. /// <summary>
  126. /// Gets a value indicating whether the current stream supports reading.
  127. /// </summary>
  128. /// <returns>
  129. /// <see langword="true"/>.
  130. /// </returns>
  131. /// <remarks>
  132. /// It is safe to read from <see cref="ShellStream"/> even after disposal.
  133. /// </remarks>
  134. public override bool CanRead
  135. {
  136. get { return true; }
  137. }
  138. /// <summary>
  139. /// Gets a value indicating whether the current stream supports seeking.
  140. /// </summary>
  141. /// <returns>
  142. /// <see langword="false"/>.
  143. /// </returns>
  144. public override bool CanSeek
  145. {
  146. get { return false; }
  147. }
  148. /// <summary>
  149. /// Gets a value indicating whether the current stream supports writing.
  150. /// </summary>
  151. /// <returns>
  152. /// <see langword="true"/> if this stream has not been disposed and the underlying channel
  153. /// is still open, otherwise <see langword="false"/>.
  154. /// </returns>
  155. /// <remarks>
  156. /// A value of <see langword="true"/> does not necessarily mean a write will succeed. It is possible
  157. /// that the channel is closed and/or the stream is disposed by another thread between a call to
  158. /// <see cref="CanWrite"/> and the call to write.
  159. /// </remarks>
  160. public override bool CanWrite
  161. {
  162. get { return !_disposed; }
  163. }
  164. /// <summary>
  165. /// This method does nothing.
  166. /// </summary>
  167. public override void Flush()
  168. {
  169. ThrowIfDisposed();
  170. Debug.Assert(_writeLength >= 0 && _writeLength <= _writeBuffer.Length);
  171. if (_writeLength > 0)
  172. {
  173. _channel.SendData(_writeBuffer, 0, _writeLength);
  174. _writeLength = 0;
  175. }
  176. }
  177. /// <summary>
  178. /// Gets the number of bytes currently available for reading.
  179. /// </summary>
  180. /// <returns>A long value representing the length of the stream in bytes.</returns>
  181. public override long Length
  182. {
  183. get
  184. {
  185. lock (_sync)
  186. {
  187. AssertValid();
  188. return _readTail - _readHead;
  189. }
  190. }
  191. }
  192. /// <summary>
  193. /// This property always returns 0, and throws <see cref="NotSupportedException"/>
  194. /// when calling the setter.
  195. /// </summary>
  196. /// <returns>
  197. /// 0.
  198. /// </returns>
  199. /// <exception cref="NotSupportedException">The setter is called.</exception>
  200. #pragma warning disable SA1623 // The property's documentation should begin with 'Gets or sets'
  201. public override long Position
  202. #pragma warning restore SA1623 // The property's documentation should begin with 'Gets or sets'
  203. {
  204. get { return 0; }
  205. set { throw new NotSupportedException(); }
  206. }
  207. /// <summary>
  208. /// This method always throws <see cref="NotSupportedException"/>.
  209. /// </summary>
  210. /// <param name="offset">A byte offset relative to the <paramref name="origin"/> parameter.</param>
  211. /// <param name="origin">A value of type <see cref="SeekOrigin"/> indicating the reference point used to obtain the new position.</param>
  212. /// <returns>Never.</returns>
  213. /// <exception cref="NotSupportedException">Always.</exception>
  214. public override long Seek(long offset, SeekOrigin origin)
  215. {
  216. throw new NotSupportedException();
  217. }
  218. /// <summary>
  219. /// This method always throws <see cref="NotSupportedException"/>.
  220. /// </summary>
  221. /// <param name="value">The desired length of the current stream in bytes.</param>
  222. /// <exception cref="NotSupportedException">Always.</exception>
  223. public override void SetLength(long value)
  224. {
  225. throw new NotSupportedException();
  226. }
  227. /// <summary>
  228. /// Expects the specified expression and performs action when one is found.
  229. /// </summary>
  230. /// <param name="expectActions">The expected expressions and actions to perform.</param>
  231. public void Expect(params ExpectAction[] expectActions)
  232. {
  233. Expect(Timeout.InfiniteTimeSpan, expectActions);
  234. }
  235. /// <summary>
  236. /// Expects the specified expression and performs action when one is found.
  237. /// </summary>
  238. /// <param name="timeout">Time to wait for input. Must non-negative or equal to -1 millisecond (for infinite timeout).</param>
  239. /// <param name="expectActions">The expected expressions and actions to perform, if the specified time elapsed and expected condition have not met, that method will exit without executing any action.</param>
  240. /// <remarks>
  241. /// If a TimeSpan representing -1 millisecond is specified for the <paramref name="timeout"/> parameter,
  242. /// this method blocks indefinitely until either the regex matches the data in the buffer, or the stream
  243. /// is closed (via disposal or via the underlying channel closing).
  244. /// </remarks>
  245. public void Expect(TimeSpan timeout, params ExpectAction[] expectActions)
  246. {
  247. _ = ExpectRegex(timeout, lookback: -1, expectActions);
  248. }
  249. /// <summary>
  250. /// Expects the specified expression and performs action when one is found.
  251. /// </summary>
  252. /// <param name="timeout">Time to wait for input. Must non-negative or equal to -1 millisecond (for infinite timeout).</param>
  253. /// <param name="lookback">The amount of data to search through from the most recent data in the buffer, or -1 to always search the entire buffer.</param>
  254. /// <param name="expectActions">The expected expressions and actions to perform, if the specified time elapsed and expected condition have not met, that method will exit without executing any action.</param>
  255. /// <remarks>
  256. /// <para>
  257. /// If a TimeSpan representing -1 millisecond is specified for the <paramref name="timeout"/> parameter,
  258. /// this method blocks indefinitely until either the regex matches the data in the buffer, or the stream
  259. /// is closed (via disposal or via the underlying channel closing).
  260. /// </para>
  261. /// <para>
  262. /// Use the <paramref name="lookback"/> parameter to constrain the search space to a fixed-size rolling window at the end of the buffer.
  263. /// This can reduce the amount of work done in cases where lots of output from the shell is expected to be received before the matching expression is found.
  264. /// </para>
  265. /// <para>
  266. /// Note: in situations with high volumes of data and a small value for <paramref name="lookback"/>, some data may not be searched through.
  267. /// It is recommended to set <paramref name="lookback"/> to a large enough value to be able to search all data as it comes in,
  268. /// but which still places a limit on the amount of work needed.
  269. /// </para>
  270. /// </remarks>
  271. public void Expect(TimeSpan timeout, int lookback, params ExpectAction[] expectActions)
  272. {
  273. _ = ExpectRegex(timeout, lookback, expectActions);
  274. }
  275. /// <summary>
  276. /// Expects the expression specified by text.
  277. /// </summary>
  278. /// <param name="text">The text to expect.</param>
  279. /// <returns>
  280. /// The text available in the shell up to and including the expected text,
  281. /// or <see langword="null"/> if the the stream is closed without a match.
  282. /// </returns>
  283. public string? Expect(string text)
  284. {
  285. return Expect(text, Timeout.InfiniteTimeSpan);
  286. }
  287. /// <summary>
  288. /// Expects the expression specified by text.
  289. /// </summary>
  290. /// <param name="text">The text to expect.</param>
  291. /// <param name="timeout">Time to wait for input. Must non-negative or equal to -1 millisecond (for infinite timeout).</param>
  292. /// <param name="lookback">The amount of data to search through from the most recent data in the buffer, or -1 to always search the entire buffer.</param>
  293. /// <returns>
  294. /// The text available in the shell up to and including the expected expression,
  295. /// or <see langword="null"/> if the specified time has elapsed or the stream is closed
  296. /// without a match.
  297. /// </returns>
  298. /// <remarks><inheritdoc cref="Expect(TimeSpan, int, ExpectAction[])"/></remarks>
  299. public string? Expect(string text, TimeSpan timeout, int lookback = -1)
  300. {
  301. ValidateTimeout(timeout);
  302. ValidateLookback(lookback);
  303. var timeoutTime = DateTime.Now.Add(timeout);
  304. var expectBytes = _encoding.GetBytes(text);
  305. lock (_sync)
  306. {
  307. while (true)
  308. {
  309. AssertValid();
  310. var searchHead = lookback == -1
  311. ? _readHead
  312. : Math.Max(_readTail - lookback, _readHead);
  313. Debug.Assert(_readHead <= searchHead && searchHead <= _readTail);
  314. #if NETFRAMEWORK || NETSTANDARD2_0
  315. var indexOfMatch = _readBuffer.IndexOf(expectBytes, searchHead, _readTail - searchHead);
  316. #else
  317. var indexOfMatch = _readBuffer.AsSpan(searchHead, _readTail - searchHead).IndexOf(expectBytes);
  318. #endif
  319. if (indexOfMatch >= 0)
  320. {
  321. var returnText = _encoding.GetString(_readBuffer, _readHead, searchHead - _readHead + indexOfMatch + expectBytes.Length);
  322. _readHead = searchHead + indexOfMatch + expectBytes.Length;
  323. AssertValid();
  324. return returnText;
  325. }
  326. if (_disposed)
  327. {
  328. return null;
  329. }
  330. if (timeout == Timeout.InfiniteTimeSpan)
  331. {
  332. Monitor.Wait(_sync);
  333. }
  334. else
  335. {
  336. var waitTimeout = timeoutTime - DateTime.Now;
  337. if (waitTimeout < TimeSpan.Zero || !Monitor.Wait(_sync, waitTimeout))
  338. {
  339. return null;
  340. }
  341. }
  342. }
  343. }
  344. }
  345. /// <summary>
  346. /// Expects the expression specified by regular expression.
  347. /// </summary>
  348. /// <param name="regex">The regular expression to expect.</param>
  349. /// <returns>
  350. /// The text available in the shell up to and including the expected expression,
  351. /// or <see langword="null"/> if the stream is closed without a match.
  352. /// </returns>
  353. public string? Expect(Regex regex)
  354. {
  355. return Expect(regex, Timeout.InfiniteTimeSpan);
  356. }
  357. /// <summary>
  358. /// Expects the expression specified by regular expression.
  359. /// </summary>
  360. /// <param name="regex">The regular expression to expect.</param>
  361. /// <param name="timeout">Time to wait for input. Must non-negative or equal to -1 millisecond (for infinite timeout).</param>
  362. /// <param name="lookback">The amount of data to search through from the most recent data in the buffer, or -1 to always search the entire buffer.</param>
  363. /// <returns>
  364. /// The text available in the shell up to and including the expected expression,
  365. /// or <see langword="null"/> if the specified timeout has elapsed or the stream
  366. /// is closed without a match.
  367. /// </returns>
  368. /// <remarks>
  369. /// <inheritdoc cref="Expect(TimeSpan, int, ExpectAction[])"/>
  370. /// </remarks>
  371. public string? Expect(Regex regex, TimeSpan timeout, int lookback = -1)
  372. {
  373. return ExpectRegex(timeout, lookback, [new ExpectAction(regex, s => { })]);
  374. }
  375. private string? ExpectRegex(TimeSpan timeout, int lookback, ExpectAction[] expectActions)
  376. {
  377. ValidateTimeout(timeout);
  378. ValidateLookback(lookback);
  379. var timeoutTime = DateTime.Now.Add(timeout);
  380. lock (_sync)
  381. {
  382. while (true)
  383. {
  384. AssertValid();
  385. var bufferText = _encoding.GetString(_readBuffer, _readHead, _readTail - _readHead);
  386. var searchStart = lookback == -1
  387. ? 0
  388. : Math.Max(bufferText.Length - lookback, 0);
  389. foreach (var expectAction in expectActions)
  390. {
  391. #if NET7_0_OR_GREATER
  392. var matchEnumerator = expectAction.Expect.EnumerateMatches(bufferText.AsSpan(searchStart));
  393. if (matchEnumerator.MoveNext())
  394. {
  395. var match = matchEnumerator.Current;
  396. var returnText = bufferText.Substring(0, searchStart + match.Index + match.Length);
  397. #else
  398. var match = expectAction.Expect.Match(bufferText, searchStart);
  399. if (match.Success)
  400. {
  401. var returnText = bufferText.Substring(0, match.Index + match.Length);
  402. #endif
  403. _readHead += _encoding.GetByteCount(returnText);
  404. AssertValid();
  405. expectAction.Action(returnText);
  406. return returnText;
  407. }
  408. }
  409. if (_disposed)
  410. {
  411. return null;
  412. }
  413. if (timeout == Timeout.InfiniteTimeSpan)
  414. {
  415. Monitor.Wait(_sync);
  416. }
  417. else
  418. {
  419. var waitTimeout = timeoutTime - DateTime.Now;
  420. if (waitTimeout < TimeSpan.Zero || !Monitor.Wait(_sync, waitTimeout))
  421. {
  422. return null;
  423. }
  424. }
  425. }
  426. }
  427. }
  428. /// <summary>
  429. /// Begins the expect.
  430. /// </summary>
  431. /// <param name="expectActions">The expect actions.</param>
  432. /// <returns>
  433. /// An <see cref="IAsyncResult" /> that references the asynchronous operation.
  434. /// </returns>
  435. public IAsyncResult BeginExpect(params ExpectAction[] expectActions)
  436. {
  437. return BeginExpect(Timeout.InfiniteTimeSpan, callback: null, state: null, expectActions);
  438. }
  439. /// <summary>
  440. /// Begins the expect.
  441. /// </summary>
  442. /// <param name="callback">The callback.</param>
  443. /// <param name="expectActions">The expect actions.</param>
  444. /// <returns>
  445. /// An <see cref="IAsyncResult" /> that references the asynchronous operation.
  446. /// </returns>
  447. public IAsyncResult BeginExpect(AsyncCallback? callback, params ExpectAction[] expectActions)
  448. {
  449. return BeginExpect(Timeout.InfiniteTimeSpan, callback, state: null, expectActions);
  450. }
  451. /// <summary>
  452. /// Begins the expect.
  453. /// </summary>
  454. /// <param name="callback">The callback.</param>
  455. /// <param name="state">The state.</param>
  456. /// <param name="expectActions">The expect actions.</param>
  457. /// <returns>
  458. /// An <see cref="IAsyncResult" /> that references the asynchronous operation.
  459. /// </returns>
  460. public IAsyncResult BeginExpect(AsyncCallback? callback, object? state, params ExpectAction[] expectActions)
  461. {
  462. return BeginExpect(Timeout.InfiniteTimeSpan, callback, state, expectActions);
  463. }
  464. /// <summary>
  465. /// Begins the expect.
  466. /// </summary>
  467. /// <param name="timeout">The timeout. Must non-negative or equal to -1 millisecond (for infinite timeout).</param>
  468. /// <param name="callback">The callback.</param>
  469. /// <param name="state">The state.</param>
  470. /// <param name="expectActions">The expect actions.</param>
  471. /// <returns>
  472. /// An <see cref="IAsyncResult" /> that references the asynchronous operation.
  473. /// </returns>
  474. public IAsyncResult BeginExpect(TimeSpan timeout, AsyncCallback? callback, object? state, params ExpectAction[] expectActions)
  475. {
  476. return BeginExpect(timeout, lookback: -1, callback, state, expectActions);
  477. }
  478. /// <summary>
  479. /// Begins the expect.
  480. /// </summary>
  481. /// <param name="timeout">The timeout. Must non-negative or equal to -1 millisecond (for infinite timeout).</param>
  482. /// <param name="lookback">The amount of data to search through from the most recent data in the buffer, or -1 to always search the entire buffer.</param>
  483. /// <param name="callback">The callback.</param>
  484. /// <param name="state">The state.</param>
  485. /// <param name="expectActions">The expect actions.</param>
  486. /// <returns>
  487. /// An <see cref="IAsyncResult" /> that references the asynchronous operation.
  488. /// </returns>
  489. public IAsyncResult BeginExpect(TimeSpan timeout, int lookback, AsyncCallback? callback, object? state, params ExpectAction[] expectActions)
  490. {
  491. return TaskToAsyncResult.Begin(Task.Run(() => ExpectRegex(timeout, lookback, expectActions)), callback, state);
  492. }
  493. /// <summary>
  494. /// Ends the execute.
  495. /// </summary>
  496. /// <param name="asyncResult">The async result.</param>
  497. /// <returns>
  498. /// The text available in the shell up to and including the expected expression.
  499. /// </returns>
  500. public string? EndExpect(IAsyncResult asyncResult)
  501. {
  502. return TaskToAsyncResult.End<string?>(asyncResult);
  503. }
  504. /// <summary>
  505. /// Reads the next line from the shell. If a line is not available it will block and wait for a new line.
  506. /// </summary>
  507. /// <returns>
  508. /// The line read from the shell.
  509. /// </returns>
  510. /// <remarks>
  511. /// <para>
  512. /// This method blocks indefinitely until either a line is available in the buffer, or the stream is closed
  513. /// (via disposal or via the underlying channel closing).
  514. /// </para>
  515. /// <para>
  516. /// When the stream is closed and there are no more newlines in the buffer, this method returns the remaining data
  517. /// (if any) and then <see langword="null"/> indicating that no more data is in the buffer.
  518. /// </para>
  519. /// </remarks>
  520. public string? ReadLine()
  521. {
  522. return ReadLine(Timeout.InfiniteTimeSpan);
  523. }
  524. /// <summary>
  525. /// Reads a line from the shell. If line is not available it will block the execution and will wait for new line.
  526. /// </summary>
  527. /// <param name="timeout">Time to wait for input. Must non-negative or equal to -1 millisecond (for infinite timeout).</param>
  528. /// <returns>
  529. /// The line read from the shell, or <see langword="null"/> when no input is received for the specified timeout.
  530. /// </returns>
  531. /// <remarks>
  532. /// <para>
  533. /// If a TimeSpan representing -1 millisecond is specified for the <paramref name="timeout"/> parameter, this method
  534. /// blocks indefinitely until either a line is available in the buffer, or the stream is closed (via disposal or via
  535. /// the underlying channel closing).
  536. /// </para>
  537. /// <para>
  538. /// When the stream is closed and there are no more newlines in the buffer, this method returns the remaining data
  539. /// (if any) and then <see langword="null"/> indicating that no more data is in the buffer.
  540. /// </para>
  541. /// </remarks>
  542. public string? ReadLine(TimeSpan timeout)
  543. {
  544. ValidateTimeout(timeout);
  545. var timeoutTime = DateTime.Now.Add(timeout);
  546. lock (_sync)
  547. {
  548. while (true)
  549. {
  550. AssertValid();
  551. #if NETFRAMEWORK || NETSTANDARD2_0
  552. var indexOfCr = _readBuffer.IndexOf(_carriageReturnBytes, _readHead, _readTail - _readHead);
  553. #else
  554. var indexOfCr = _readBuffer.AsSpan(_readHead, _readTail - _readHead).IndexOf(_carriageReturnBytes);
  555. #endif
  556. if (indexOfCr >= 0)
  557. {
  558. // We have found \r. We only need to search for \n up to and just after the \r
  559. // (in order to consume \r\n if we can).
  560. #if NETFRAMEWORK || NETSTANDARD2_0
  561. var indexOfLf = indexOfCr + _carriageReturnBytes.Length + _lineFeedBytes.Length <= _readTail - _readHead
  562. ? _readBuffer.IndexOf(_lineFeedBytes, _readHead, indexOfCr + _carriageReturnBytes.Length + _lineFeedBytes.Length)
  563. : _readBuffer.IndexOf(_lineFeedBytes, _readHead, indexOfCr);
  564. #else
  565. var indexOfLf = indexOfCr + _carriageReturnBytes.Length + _lineFeedBytes.Length <= _readTail - _readHead
  566. ? _readBuffer.AsSpan(_readHead, indexOfCr + _carriageReturnBytes.Length + _lineFeedBytes.Length).IndexOf(_lineFeedBytes)
  567. : _readBuffer.AsSpan(_readHead, indexOfCr).IndexOf(_lineFeedBytes);
  568. #endif
  569. if (indexOfLf >= 0 && indexOfLf < indexOfCr)
  570. {
  571. // If there is \n before the \r, then return up to the \n
  572. var returnText = _encoding.GetString(_readBuffer, _readHead, indexOfLf);
  573. _readHead += indexOfLf + _lineFeedBytes.Length;
  574. AssertValid();
  575. return returnText;
  576. }
  577. else if (indexOfLf == indexOfCr + _carriageReturnBytes.Length)
  578. {
  579. // If we have \r\n, then consume both
  580. var returnText = _encoding.GetString(_readBuffer, _readHead, indexOfCr);
  581. _readHead += indexOfCr + _carriageReturnBytes.Length + _lineFeedBytes.Length;
  582. AssertValid();
  583. return returnText;
  584. }
  585. else
  586. {
  587. // Return up to the \r
  588. var returnText = _encoding.GetString(_readBuffer, _readHead, indexOfCr);
  589. _readHead += indexOfCr + _carriageReturnBytes.Length;
  590. AssertValid();
  591. return returnText;
  592. }
  593. }
  594. else
  595. {
  596. // There is no \r. What about \n?
  597. #if NETFRAMEWORK || NETSTANDARD2_0
  598. var indexOfLf = _readBuffer.IndexOf(_lineFeedBytes, _readHead, _readTail - _readHead);
  599. #else
  600. var indexOfLf = _readBuffer.AsSpan(_readHead, _readTail - _readHead).IndexOf(_lineFeedBytes);
  601. #endif
  602. if (indexOfLf >= 0)
  603. {
  604. var returnText = _encoding.GetString(_readBuffer, _readHead, indexOfLf);
  605. _readHead += indexOfLf + _lineFeedBytes.Length;
  606. AssertValid();
  607. return returnText;
  608. }
  609. }
  610. if (_disposed)
  611. {
  612. var lastLine = _readHead == _readTail
  613. ? null
  614. : _encoding.GetString(_readBuffer, _readHead, _readTail - _readHead);
  615. _readHead = _readTail = 0;
  616. return lastLine;
  617. }
  618. if (timeout == Timeout.InfiniteTimeSpan)
  619. {
  620. _ = Monitor.Wait(_sync);
  621. }
  622. else
  623. {
  624. var waitTimeout = timeoutTime - DateTime.Now;
  625. if (waitTimeout < TimeSpan.Zero || !Monitor.Wait(_sync, waitTimeout))
  626. {
  627. return null;
  628. }
  629. }
  630. }
  631. }
  632. }
  633. private static void ValidateTimeout(TimeSpan timeout)
  634. {
  635. if (timeout < TimeSpan.Zero && timeout != Timeout.InfiniteTimeSpan)
  636. {
  637. throw new ArgumentOutOfRangeException(nameof(timeout), "Value must be non-negative or equal to -1 millisecond (for infinite timeout)");
  638. }
  639. }
  640. private static void ValidateLookback(int lookback)
  641. {
  642. if (lookback is <= 0 and not -1)
  643. {
  644. throw new ArgumentOutOfRangeException(nameof(lookback), "Value must be positive or equal to -1 (for no window)");
  645. }
  646. }
  647. private void ThrowIfDisposed()
  648. {
  649. #if NET7_0_OR_GREATER
  650. ObjectDisposedException.ThrowIf(_disposed, this);
  651. #else
  652. if (_disposed)
  653. {
  654. throw new ObjectDisposedException(GetType().FullName);
  655. }
  656. #endif // NET7_0_OR_GREATER
  657. }
  658. /// <summary>
  659. /// Reads all of the text currently available in the shell.
  660. /// </summary>
  661. /// <returns>
  662. /// The text available in the shell.
  663. /// </returns>
  664. public string Read()
  665. {
  666. lock (_sync)
  667. {
  668. AssertValid();
  669. var text = _encoding.GetString(_readBuffer, _readHead, _readTail - _readHead);
  670. _readHead = _readTail = 0;
  671. return text;
  672. }
  673. }
  674. /// <inheritdoc/>
  675. public override int Read(byte[] buffer, int offset, int count)
  676. {
  677. lock (_sync)
  678. {
  679. while (_readHead == _readTail && !_disposed)
  680. {
  681. _ = Monitor.Wait(_sync);
  682. }
  683. AssertValid();
  684. var bytesRead = Math.Min(count, _readTail - _readHead);
  685. Buffer.BlockCopy(_readBuffer, _readHead, buffer, offset, bytesRead);
  686. _readHead += bytesRead;
  687. AssertValid();
  688. return bytesRead;
  689. }
  690. }
  691. /// <summary>
  692. /// Writes the specified text to the shell.
  693. /// </summary>
  694. /// <param name="text">The text to be written to the shell.</param>
  695. /// <remarks>
  696. /// If <paramref name="text"/> is <see langword="null"/>, nothing is written.
  697. /// Otherwise, <see cref="Flush"/> is called after writing the data to the buffer.
  698. /// </remarks>
  699. /// <exception cref="ObjectDisposedException">The stream is closed.</exception>
  700. public void Write(string? text)
  701. {
  702. if (text is null)
  703. {
  704. return;
  705. }
  706. var data = _encoding.GetBytes(text);
  707. Write(data, 0, data.Length);
  708. Flush();
  709. }
  710. /// <inheritdoc/>
  711. public override void Write(byte[] buffer, int offset, int count)
  712. {
  713. ThrowIfDisposed();
  714. while (count > 0)
  715. {
  716. if (_writeLength == _writeBuffer.Length)
  717. {
  718. Flush();
  719. }
  720. var bytesToCopy = Math.Min(count, _writeBuffer.Length - _writeLength);
  721. Buffer.BlockCopy(buffer, offset, _writeBuffer, _writeLength, bytesToCopy);
  722. offset += bytesToCopy;
  723. count -= bytesToCopy;
  724. _writeLength += bytesToCopy;
  725. Debug.Assert(_writeLength >= 0 && _writeLength <= _writeBuffer.Length);
  726. }
  727. }
  728. /// <summary>
  729. /// Writes the line to the shell.
  730. /// </summary>
  731. /// <param name="line">The line to be written to the shell.</param>
  732. /// <remarks>
  733. /// If <paramref name="line"/> is <see langword="null"/>, only the line terminator is written.
  734. /// <see cref="Flush"/> is called once the data is written.
  735. /// </remarks>
  736. /// <exception cref="ObjectDisposedException">The stream is closed.</exception>
  737. public void WriteLine(string line)
  738. {
  739. Write(line + "\r");
  740. }
  741. /// <inheritdoc/>
  742. protected override void Dispose(bool disposing)
  743. {
  744. if (!disposing)
  745. {
  746. base.Dispose(disposing);
  747. return;
  748. }
  749. lock (_sync)
  750. {
  751. if (_disposed)
  752. {
  753. return;
  754. }
  755. _disposed = true;
  756. // Do not dispose _session (we don't own it)
  757. _session.Disconnected -= Session_Disconnected;
  758. _session.ErrorOccured -= Session_ErrorOccured;
  759. // But we do own _channel
  760. _channel.DataReceived -= Channel_DataReceived;
  761. _channel.Closed -= Channel_Closed;
  762. _channel.Dispose();
  763. Monitor.PulseAll(_sync);
  764. }
  765. base.Dispose(disposing);
  766. }
  767. private void Session_ErrorOccured(object? sender, ExceptionEventArgs e)
  768. {
  769. ErrorOccurred?.Invoke(this, e);
  770. }
  771. private void Session_Disconnected(object? sender, EventArgs e)
  772. {
  773. Dispose();
  774. }
  775. private void Channel_Closed(object? sender, ChannelEventArgs e)
  776. {
  777. Dispose();
  778. if (Closed != null)
  779. {
  780. // Handle event on different thread
  781. ThreadAbstraction.ExecuteThread(() => Closed?.Invoke(this, EventArgs.Empty));
  782. }
  783. }
  784. private void Channel_DataReceived(object? sender, ChannelDataEventArgs e)
  785. {
  786. lock (_sync)
  787. {
  788. AssertValid();
  789. // Ensure sufficient buffer space and copy the new data in.
  790. if (_readBuffer.Length - _readTail >= e.Data.Length)
  791. {
  792. // If there is enough space after _tail for the new data,
  793. // then copy the data there.
  794. Buffer.BlockCopy(e.Data, 0, _readBuffer, _readTail, e.Data.Length);
  795. _readTail += e.Data.Length;
  796. }
  797. else
  798. {
  799. // We can't fit the new data after _tail.
  800. var newLength = _readTail - _readHead + e.Data.Length;
  801. if (newLength <= _readBuffer.Length)
  802. {
  803. // If there is sufficient space at the start of the buffer,
  804. // then move the current data to the start of the buffer.
  805. Buffer.BlockCopy(_readBuffer, _readHead, _readBuffer, 0, _readTail - _readHead);
  806. }
  807. else
  808. {
  809. // Otherwise, we're gonna need a bigger buffer.
  810. var newBuffer = new byte[Math.Max(newLength, _readBuffer.Length * 2)];
  811. Buffer.BlockCopy(_readBuffer, _readHead, newBuffer, 0, _readTail - _readHead);
  812. _readBuffer = newBuffer;
  813. }
  814. // Copy the new data into the freed-up space.
  815. Buffer.BlockCopy(e.Data, 0, _readBuffer, _readTail - _readHead, e.Data.Length);
  816. _readHead = 0;
  817. _readTail = newLength;
  818. }
  819. AssertValid();
  820. Monitor.PulseAll(_sync);
  821. }
  822. DataReceived?.Invoke(this, new ShellDataEventArgs(e.Data));
  823. }
  824. }
  825. }