1
0
Fork 0

Fun experiment :P

This commit is contained in:
Mo 2024-04-11 19:09:07 +02:00
commit 2a01ad16a5
4 changed files with 94 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
/Cargo.lock

4
Cargo.toml Normal file
View file

@ -0,0 +1,4 @@
[package]
name = "ssh-shell-cmd-runner"
edition = "2021"
publish = false

1
README.md Normal file
View file

@ -0,0 +1 @@
A small experiment about running multiple shell commands over SSH using the same connection.

87
src/main.rs Normal file
View file

@ -0,0 +1,87 @@
use std::{
io::{self, BufRead, BufReader, Write},
process::{exit, Child, ChildStderr, ChildStdin, ChildStdout, Command, Stdio},
};
struct Runner {
stdin: ChildStdin,
stdout: BufReader<ChildStdout>,
stderr: BufReader<ChildStderr>,
process: Child,
line_buf: String,
}
// Print every line of `reader` until the end or until the line "CMD_DONE" is found.
fn print_until_cmd_done(mut reader: impl BufRead, buf: &mut String) -> io::Result<()> {
loop {
buf.clear();
let n = reader.read_line(buf)?;
if n == 0 || buf.as_bytes() == b"CMD_DONE\n" {
return Ok(());
}
print!("{buf}");
}
}
impl Runner {
fn build(host: &str) -> io::Result<Self> {
let mut process = Command::new("ssh")
.arg(host)
.arg("bash")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
Ok(Self {
stdin: process.stdin.take().unwrap(),
stdout: BufReader::new(process.stdout.take().unwrap()),
stderr: BufReader::new(process.stderr.take().unwrap()),
process,
line_buf: String::with_capacity(1024),
})
}
/// WARNING: Command injection is possible! Don't use it with untrusted input!
fn run_shell_command(&mut self, cmd: &str) -> io::Result<()> {
println!("\n\nRunning `{cmd}`");
writeln!(self.stdin, "{cmd}")?;
// Send CMD_DONE to stdout and stderr to be able to know when the output of a command ends.
writeln!(self.stdin, "echo CMD_DONE")?;
writeln!(self.stdin, "echo CMD_DONE >&2")?;
println!("stdout:");
print_until_cmd_done(&mut self.stdout, &mut self.line_buf)?;
println!("\nstderr:");
print_until_cmd_done(&mut self.stderr, &mut self.line_buf)?;
Ok(())
}
}
impl Drop for Runner {
fn drop(&mut self) {
self.run_shell_command("exit").unwrap();
self.process.wait().unwrap();
}
}
fn main() -> io::Result<()> {
// ENTER THE HOST
let host = "";
if host.is_empty() {
eprintln!("No host specified!");
exit(1);
}
let mut runner = Runner::build(host)?;
runner.run_shell_command("ls -al")?;
runner.run_shell_command("cd .config")?;
runner.run_shell_command("ls -al")?;
Ok(())
}