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

/bin/sh output is not what expected #15

Closed
the-ssd opened this issue Jul 2, 2023 · 12 comments
Closed

/bin/sh output is not what expected #15

the-ssd opened this issue Jul 2, 2023 · 12 comments
Labels
question Further information is requested

Comments

@the-ssd
Copy link

the-ssd commented Jul 2, 2023

expectation:

sh-5.1$ echo test
test

reality:

sh-5.1$ 
test

(this works with fish instead of sh)

also when starting shell without writing anything to it, you would expect

sh-5.1$

but instead it is empty (same on fish)

this is important for ssh, which is what I am trying to do
there is portabe-pty (I am currently using it), but it has a lot of dependencies (includes nix)
and because I am writing it for Redox OS
I will need to port multiple dependencies, some of which are 4–6 years old

while for ptyprocess it's just nix (still needs porting)
if you could fix these 2 problems it will be really helpful :)

@zhiburt
Copy link
Owner

zhiburt commented Jul 2, 2023

Hi @the-ssd could you provide the code your using?

@zhiburt
Copy link
Owner

zhiburt commented Jul 2, 2023

PS: I've also had some plans to make a SSH api zhiburt/expectrl#23, but after looking through some analogues I got to worry that it's harder then it seems.

@the-ssd
Copy link
Author

the-ssd commented Jul 2, 2023

use std::thread;
use std::time::Duration;
use ptyprocess::PtyProcess;
use std::io::{BufReader, Result, Write, Read};
use std::process::Command;


fn main() -> Result<()> {
    // spawn a cat process
    let process = PtyProcess::spawn(Command::new("/bin/sh"))?;

    // stream for reading
    let stream = process.get_raw_handle()?;
    // thread for reading (needed for ssh to work)
    thread::spawn(move || {
        let mut reader = BufReader::new(stream);

        loop {
            let mut buf = [0; 1];
            let _ = reader.read_exact(&mut buf);
            // in ssh this would be set data to client
            print!("{}", String::from_utf8_lossy(&buf));
        }

    });

    // create a new communication stream for writing
    let mut stream = process.get_raw_handle()?;

    thread::sleep(Duration::from_secs(1));
    // send a message to process
    writeln!(stream, "echo test")?;

    // wait to read output from the stream
    thread::sleep(Duration::from_secs(1));

    Ok(())
}

@the-ssd
Copy link
Author

the-ssd commented Jul 2, 2023

Also, I have a working ssh server (works with OpenSSH), but I am having problems with the client

@zhiburt
Copy link
Owner

zhiburt commented Jul 2, 2023

So I've seemed to figured it out.
You can do the following.

    let mut process = PtyProcess::spawn(Command::new("/bin/sh"))?;
    process.set_echo(true, None).unwrap();

@zhiburt
Copy link
Owner

zhiburt commented Jul 2, 2023

PS: I'd recommend you to use expectrl instead if you need a high level API.

@zhiburt zhiburt added the question Further information is requested label Jul 2, 2023
@the-ssd
Copy link
Author

the-ssd commented Jul 3, 2023

Thanks, this solved the first problem, but there is still the other one

use std::thread;
use std::time::Duration;
use ptyprocess::PtyProcess;
use std::io::{BufReader, Result, Write, Read};
use std::process::Command;


fn main() -> Result<()> {
    // spawn a process
    let mut process = PtyProcess::spawn(Command::new("/bin/sh"))?;
    process.set_echo(true, None).unwrap();
    

    // stream for reading
    let stream = process.get_pty_stream()?;
    // thread for reading (needed for ssh to work)
    thread::spawn(move || {
        let mut reader = BufReader::new(stream);

        loop {
            let mut buf = [0; 1];
            let _ = reader.read_exact(&mut buf);
            // in ssh this would be set data to client
            print!("{}", String::from_utf8_lossy(&buf));
        }

    });
    
    // wait to read output from the stream
    thread::sleep(Duration::from_secs(5));

    Ok(())
}

this should return sh-5.1$ before the program exits (in this case before 5 sec delay)
but instead it is empty

@zhiburt
Copy link
Owner

zhiburt commented Jul 3, 2023

Seems like it works for me?

sh-5.1$ 

Maybe you shall increase the delay.

@zhiburt
Copy link
Owner

zhiburt commented Jul 3, 2023

PS: I am not sure what you're trying to achieve with additional thread. But you could use expectrl here as follows.

use expectrl::repl::ReplSession;

fn main() {
    let mut p = expectrl::spawn("sh").unwrap();
    p.get_process_mut().set_echo(true, None).unwrap();

    let mut shell = ReplSession::new(p, String::from("sh-5.1$"), Some(String::from("exit")), true);

    shell.expect_prompt().unwrap();

    let output = exec(&mut shell, "echo Hello World");
    println!("{:?}", output);

    let output = exec(&mut shell, "echo '2 + 3' | bc");
    println!("{:?}", output);
}

fn exec(shell: &mut ReplSession, cmd: &str) -> String {
    let buf = shell.execute(cmd).unwrap();
    let mut string = String::from_utf8_lossy(&buf).into_owned();
    string = string.replace("\r\n\u{1b}[?2004l\r", "");
    string = string.replace("\r\n\u{1b}[?2004h", "");

    string
}
"Hello World"
"5"

@the-ssd
Copy link
Author

the-ssd commented Jul 3, 2023

Some shells have autocomplete so expectrl won't work :(
reading from a stream is going to block the thread, so an extra thread is needed
but writing is ok

it will be empty then write sh-5.1$ and exit
but it should write sh-5.1$ then wait and exit

@zhiburt
Copy link
Owner

zhiburt commented Jul 3, 2023

it will be empty then write sh-5.1$ and exit
but it should write sh-5.1$ then wait and exit

True

But not exactly, I've just got to remember that terminals often do some buffering.
So to see what you want to just change print! to println!.

Some shells have autocomplete to expectrl won't work :(

Sorry, I don't get it, could you explain a bit?
I mean some have autocomplete but then what?

@the-ssd
Copy link
Author

the-ssd commented Jul 3, 2023

So to run a remote shell that has autocomplete, I can't just wait for the full command because then autocomplete doesn't work.
Each input (key) is received separately and has to be written to pty.
Reading has to be done is a separate thread because of blocking.

Changing print! to println! worked, thanks!

std::io::stdout().flush().unwrap(); // this also worked

@the-ssd the-ssd closed this as completed Jul 3, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants