From 03bbbbddc4400a6aae18da4ca0b3f39fd057cabc Mon Sep 17 00:00:00 2001 From: Daniel Bulant Date: Sun, 28 May 2023 11:19:12 +0200 Subject: [PATCH] Initial commit --- .gitignore | 1 + Cargo.toml | 13 +++++++++++ src/main.rs | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..3140bd5 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "tcp-spawner" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tokio = { version = "1", features = ["full"] } +tokio-stream = "0.1" +os_pipe = "1.1.4" +tokio-util = { version = "0.7.8", features = ["compat"] } +futures-util = { version = "0.3.28", features = ["io"] } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..3924aa0 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,62 @@ +use std::env::args; +use std::net::TcpListener; +use tokio::process::Command; +use tokio::io; +use futures_util::io::AllowStdIo; + + +fn build_command_from_args(args: &[String]) -> Command { + let mut command = Command::new(args.get(0).unwrap()); + + command.stdout(std::process::Stdio::piped()); + command.stderr(std::process::Stdio::piped()); + + for arg in args.iter().skip(1) { + command.arg(arg); + } + + command +} + +#[tokio::main] +/// This is a simple TCP server that will spawn a process for each connection +/// and pipe the connection to the process stdin and stdout. +/// The first argument is the port to listen on. +/// The second argument (and later arguments) is the command to run. +async fn main() -> io::Result<()> { + let args = args().collect::>(); + let listener = TcpListener::bind(args.get(1).unwrap())?; + let listener = tokio::net::TcpListener::from_std(listener)?; + + loop { + let (mut stream, _) = listener.accept().await.unwrap(); + let mut command = build_command_from_args(args.get(2..).unwrap()); + + println!("Got connection from {:?}", stream.peer_addr()); + + tokio::spawn(async move { + let (mut stream_reader, mut stream_writer) = stream.split(); + + let (child_reader, child_writer) = os_pipe::pipe().unwrap(); + let writer_clone = child_writer.try_clone().unwrap(); + command.stdout(child_writer); + command.stderr(writer_clone); + + let child_reader = AllowStdIo::new(child_reader); + let mut child_reader = tokio_util::compat::FuturesAsyncReadCompatExt::compat(child_reader); + + let mut child = command.spawn().unwrap(); + + let mut stdin = child.stdin.take().unwrap(); + + let copy_stdin = io::copy(&mut stream_reader, &mut stdin); + let copy_output = io::copy(&mut child_reader, &mut stream_writer); + + drop(command); + + tokio::try_join!(copy_stdin, copy_output).unwrap(); + + child.wait().await.unwrap(); + }); + } +}