use std::process::Stdio; use tokio::{ io::{AsyncRead, AsyncReadExt}, process::Command, }; pub async fn execute(command: &str, max_output_bytes: u64) -> (i32, String, String) { let mut child = match Command::new("sh") .arg("-c") .arg(command) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() { Ok(child) => child, Err(error) => return (-1, String::new(), error.to_string()), }; let Some(stdout) = child.stdout.take() else { return (-1, String::new(), "stdout pipe unavailable".to_string()); }; let Some(stderr) = child.stderr.take() else { return (-1, String::new(), "stderr pipe unavailable".to_string()); }; let (stdout_data, stderr_data) = tokio::join!( read_limited_output(stdout, max_output_bytes), read_limited_output(stderr, max_output_bytes), ); child.kill().await.ok(); let exit_code = child .wait() .await .ok() .and_then(|status| status.code()) .unwrap_or(-1); ( exit_code, String::from_utf8_lossy(&stdout_data).into_owned(), String::from_utf8_lossy(&stderr_data).into_owned(), ) } async fn read_limited_output(reader: R, max_output_bytes: u64) -> Vec where R: AsyncRead + Unpin, { let mut buffer = Vec::new(); let _ = reader.take(max_output_bytes).read_to_end(&mut buffer).await; buffer }