From 2a01ad16a54fd3be8324a6e6fffd0138977ee988 Mon Sep 17 00:00:00 2001 From: mo8it Date: Thu, 11 Apr 2024 19:09:07 +0200 Subject: [PATCH] Fun experiment :P --- .gitignore | 2 ++ Cargo.toml | 4 +++ README.md | 1 + src/main.rs | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..bdef609 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,4 @@ +[package] +name = "ssh-shell-cmd-runner" +edition = "2021" +publish = false diff --git a/README.md b/README.md new file mode 100644 index 0000000..c1f7578 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +A small experiment about running multiple shell commands over SSH using the same connection. diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..1815473 --- /dev/null +++ b/src/main.rs @@ -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, + stderr: BufReader, + 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 { + 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(()) +}