pkgw/stund

Passing through control characters with tokio-pty-process

Opened this issue · 2 comments

Is there a simple example of using tokio-pty-process where everything is passed through?

I finally figured out I wanted the spawn_pty_async_raw method instead of non-raw and that solved a lot of issues for me.

I noticed if I pressed ctrl+c (^C), it will interrupt the whole program, not what's running in my interactive shell (bash).

For example:

$ cargo run
# starts a child process `bash`
[jerome@jerome-arch ptyfun]$ ping google.com
PING google.com (172.217.13.174) 56(84) bytes of data.
64 bytes from yul03s04-in-f14.1e100.net (172.217.13.174): icmp_seq=1 ttl=53 time=17.9 ms
64 bytes from yul03s04-in-f14.1e100.net (172.217.13.174): icmp_seq=2 ttl=53 time=18.7 ms
^C
# kicked back to my host shell 
$

Here's the code I'm using:

use failure::Error;
use futures::{Future, Stream};
use std::io::Write;
use tokio::codec::{BytesCodec, FramedRead};
use tokio::io::AsyncWrite;

use tokio_pty_process::{AsyncPtyMaster, CommandExt};

fn main() -> Result<(), Error> {
    let mut runtime = tokio::runtime::Builder::new()
        .blocking_threads(1)
        .core_threads(1)
        .build()
        .unwrap();
    let (stdin, mut stdout) = (tokio::io::stdin(), std::io::stdout());

    let mut command = std::process::Command::new("bash");
    let ptmx = AsyncPtyMaster::open()?;
    let child = command.spawn_pty_async_raw(&ptmx)?;
    let (pty_read, mut pty_write) = ptmx.split();

    let fut = FramedRead::new(stdin, BytesCodec::new()).for_each(move |b| {
        pty_write.poll_write(&b)?;
        pty_write.flush()?;
        Ok(())
    });

    runtime.spawn(
        FramedRead::new(pty_read, BytesCodec::new())
            .for_each(move |b| {
                stdout.write(&b)?;
                stdout.flush()?;
                Ok(())
            })
            .map_err(|e| println!("PTY READ ERROR: {}", e)),
    );
    runtime.spawn(fut.map(|_| {}).map_err(|e| println!("ERROR: {}", e)));

    runtime.block_on(child).unwrap();

    Ok(())
}

I'm doing a lot of manual IO forwarding (and often in a synchronous manner) because using a Sink just buffered everything forever. I'm starting to think I need to go even lower level and sending byte by byte so things like exiting top by pressing q will just work (right now I need to do q + press enter for the framed read to read anything).

Any advice appreciated. I barely understand how PTYs or TTYs work.

pkgw commented

I'm afraid that I'm not sure how helpful I can be — I only know enough about PTYs and TTYs to have gotten stund working for me!

I do know that for control characters, yes, "raw" vs. "cooked" mode is an important distinction.

For ^C, I believe that the way things work is that every process in the process group gets a SIGINT, and I think that shells and SSH just ignore that signal when they receive it. Or, depending on how things are wired up, they may have to forward the signal onto their child processes as appropriate.

The goal is that tokio-pty-process will make it easy to do the kinds of things that you're attempting, but I'm afraid I don't have a lot of example code — the stund use case is pretty limited.

Perhaps this document might be helpful? I haven't read it, but it looks like a solid overview of TTY intricacies. I have a strong hunch that some background reading will be helpful here, since I think a lot of the PTY/TTY stuff are random "that's how it is" things, not super sensible interfaces.

Thanks, that helped!

I've tried forwarding signals to the child process, now at least my rust process will not be interrupted, but bash is still not interrupting. I tried both with the child process' PID and with a negative PID (to send the signal to the whole group), still no luck.

I also tried writing control characters directly, ASCII encoded. It doesn't appear to do much either.

I'm not entirely sure what to try next. I'll keep reading the link you sent.

Edit: Running in "cooked" mode somehow gives me the desired behavior when sending the ASCII encoded control characters. Unfortunately, it prints everything I type twice.