Skip to content
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

Passing through control characters with tokio-pty-process #94

Open
jeromegn opened this issue Jul 23, 2019 · 2 comments
Open

Passing through control characters with tokio-pty-process #94

jeromegn opened this issue Jul 23, 2019 · 2 comments

Comments

@jeromegn
Copy link

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
Copy link
Owner

pkgw commented Jul 23, 2019

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.

@jeromegn
Copy link
Author

jeromegn commented Jul 24, 2019

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants