openssh-rust / openssh

Scriptable SSH through OpenSSH in Rust
Apache License 2.0
232 stars 37 forks source link

Async read of a ssh program stdio. #93

Open redragonx opened 2 years ago

redragonx commented 2 years ago

How do I go about this the right way? I would like to see an example such that it reads stdio and writes the data to a file in real time.

Here's my current thought process on how I might do the above.

  1. Setup a Command with stdio::piped for stdio, stder and stdin
  2. Spawn the ssh command
  3. get the stdio handles
  4. read the stdio and write to a file stream.
  5. Program exits How do I know that the ssh program is done?
NobodyXu commented 2 years ago

How do I know that the ssh program is done?

You need to call RemoteChild::wait.

redragonx commented 2 years ago

is wait meant to be called only once, or can you call it within a loop?

while wait is false, read stdio?

NobodyXu commented 2 years ago

is wait meant to be called only once

RemoteChild::wait takes RemoteChild by value, so it can be called only once.

Checkout RemoteChild::wait_with_output for example.

Note that you can just use tokio::spawn instead of tokio::try_join, which is more readable and might yield better performance.

NobodyXu commented 2 years ago

@redragonx Has your problem been solved by my reply?

TaaviE commented 1 year ago

Hopefully I'm not derailing this issue, @NobodyXu. I think I have the same problem, when using tokio::process::Child I can do something like this to handle stdout/stderr/stdin and also potentially wait for the process to end:

loop {
    tokio::select! {
        result = stdout_reader.next_line() => { [...] }
        result = child.wait() => { break; }
    }
}

But when trying to do the same with openssh::child::RemoteChild I can't because wait() function takes ownership of the receiver self, which moves child. What's the intended approach in this case?

NobodyXu commented 1 year ago

You would have to wrap child in an Option.

And the current implementation has a strange behavior:

If the Child is dropped before stdout/stderr is completely consumed, then the pending data won't be flushed.

This is because Child holds an unix connection to the ssh multiplex server and once it is closed, the ssh multiplex server considers the multiplex session as dead and won't flush any data.

I recommend to just read from stdout until it reaches EOF.

NobodyXu commented 1 year ago

I can fix this with a breaking change if people really need this.

redragonx commented 1 year ago

Sorry @NobodyXu

I got distracted with life changes and haven't been to dig into this since then.

NobodyXu commented 1 year ago

Sorry @NobodyXu

I got distracted with life changes and haven't been to dig into this since then.

No worries, it's open source not coop work, you can do that at any time.

TaaviE commented 1 year ago

You would have to wrap child in an Option.

I'm not really sure how that would be done in this case, sorry.

I recommend to just read from stdout until it reaches EOF.

I'd do that but I pretty much need to handle stdin/stdout/stderr more than once while the process is already running.

NobodyXu commented 1 year ago

I'd do that but I pretty much need to handle stdin/stdout/stderr more than once while the process is already running.

Maybe you can read in a loop?

TaaviE commented 1 year ago

Maybe you can read in a loop?

Yes, this works to a large extent.

Though I kind-of think that it would be much nicer if one could use the same syntax and semantics for both remote SSH and tokio::process::Child.

NobodyXu commented 1 year ago

Though I kind-of think that it would be much nicer if one could use the same syntax and semantics for both remote SSH and tokio::process::Child.

It will be fixed by https://github.com/openssh-rust/openssh-mux-client/issues/6 though it has been stale recently, blocking on new release of external dependency tokio-util.

TaaviE commented 1 year ago

That's great to hear, will subscribe to that issue.