use std::path::Path;
use std::process::Stdio;
use command_fds::{CommandFdExt, FdMapping};
use tokio::net::unix::pipe;
use tokio::process::{Child, ChildStderr, ChildStdout, Command};
use crate::{Error, Result};
pub struct Spawned {
pub child: Child,
pub writer: pipe::Sender,
pub reader: pipe::Receiver,
pub stdout: ChildStdout,
pub stderr: ChildStderr,
}
#[allow(clippy::unused_async)]
pub async fn spawn(program: &Path, args: &[String], envs: &[(String, String)]) -> Result<Spawned> {
let (cmd_reader, cmd_writer) =
os_pipe::pipe().map_err(|e| Error::Transport(format!("创建 cmd 管道失败: {e}")))?;
let (resp_reader, resp_writer) =
os_pipe::pipe().map_err(|e| Error::Transport(format!("创建 resp 管道失败: {e}")))?;
let mut command = Command::new(program);
command
.args(args)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.kill_on_drop(true);
for (k, v) in envs {
command.env(k, v);
}
command
.fd_mappings(vec![
FdMapping {
parent_fd: cmd_reader.into(),
child_fd: 3,
},
FdMapping {
parent_fd: resp_writer.into(),
child_fd: 4,
},
])
.map_err(|e| Error::Transport(format!("fd 映射冲突: {e}")))?;
let mut child = command
.spawn()
.map_err(|e| Error::Transport(format!("启动子进程失败: {e}")))?;
drop(command);
let stdout = child
.stdout
.take()
.ok_or_else(|| Error::Transport("无法捕获子进程 stdout".into()))?;
let stderr = child
.stderr
.take()
.ok_or_else(|| Error::Transport("无法捕获子进程 stderr".into()))?;
let writer = pipe::Sender::from_owned_fd(cmd_writer.into())
.map_err(|e| Error::Transport(format!("包裹命令写端失败: {e}")))?;
let reader = pipe::Receiver::from_owned_fd(resp_reader.into())
.map_err(|e| Error::Transport(format!("包裹响应读端失败: {e}")))?;
Ok(Spawned {
child,
writer,
reader,
stdout,
stderr,
})
}