unistore_process/
child.rs

1//! 子进程管理
2//!
3//! 职责:封装子进程句柄和生命周期管理
4
5use crate::error::ProcessError;
6use crate::output::ProcessOutput;
7use std::time::Duration;
8use tokio::io::{AsyncReadExt, AsyncWriteExt};
9use tokio::process::Child as TokioChild;
10
11/// 退出状态
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct ExitStatus {
14    code: Option<i32>,
15}
16
17impl ExitStatus {
18    /// 从退出码创建
19    pub fn from_code(code: i32) -> Self {
20        Self { code: Some(code) }
21    }
22
23    /// 从信号创建(Unix)
24    #[cfg(unix)]
25    pub fn from_signal(signal: i32) -> Self {
26        Self { code: Some(128 + signal) }
27    }
28
29    /// 未知退出状态
30    pub fn unknown() -> Self {
31        Self { code: None }
32    }
33
34    /// 判断是否成功
35    pub fn success(&self) -> bool {
36        self.code == Some(0)
37    }
38
39    /// 获取退出码
40    pub fn code(&self) -> Option<i32> {
41        self.code
42    }
43}
44
45impl From<std::process::ExitStatus> for ExitStatus {
46    fn from(status: std::process::ExitStatus) -> Self {
47        Self {
48            code: status.code(),
49        }
50    }
51}
52
53/// 子进程句柄
54pub struct Child {
55    inner: TokioChild,
56    pid: u32,
57}
58
59impl Child {
60    /// 从 tokio Child 创建
61    pub(crate) fn new(child: TokioChild) -> Result<Self, ProcessError> {
62        let pid = child
63            .id()
64            .ok_or_else(|| ProcessError::SpawnFailed("无法获取进程 ID".into()))?;
65
66        Ok(Self { inner: child, pid })
67    }
68
69    /// 获取进程 ID
70    pub fn id(&self) -> u32 {
71        self.pid
72    }
73
74    /// 判断进程是否仍在运行
75    pub fn is_running(&mut self) -> bool {
76        matches!(self.inner.try_wait(), Ok(None))
77    }
78
79    /// 等待进程结束
80    pub async fn wait(&mut self) -> Result<ExitStatus, ProcessError> {
81        let status = self.inner.wait().await?;
82        Ok(status.into())
83    }
84
85    /// 等待进程结束(带超时)
86    pub async fn wait_timeout(&mut self, timeout: Duration) -> Result<Option<ExitStatus>, ProcessError> {
87        match tokio::time::timeout(timeout, self.inner.wait()).await {
88            Ok(Ok(status)) => Ok(Some(status.into())),
89            Ok(Err(e)) => Err(ProcessError::from(e)),
90            Err(_) => Ok(None), // 超时
91        }
92    }
93
94    /// 等待并获取输出
95    pub async fn wait_with_output(self) -> Result<ProcessOutput, ProcessError> {
96        let output = self.inner.wait_with_output().await?;
97        Ok(ProcessOutput::new(
98            output.status.into(),
99            output.stdout,
100            output.stderr,
101        ))
102    }
103
104    /// 向标准输入写入数据
105    pub async fn write_stdin(&mut self, data: &[u8]) -> Result<(), ProcessError> {
106        if let Some(stdin) = self.inner.stdin.as_mut() {
107            stdin.write_all(data).await?;
108            stdin.flush().await?;
109            Ok(())
110        } else {
111            Err(ProcessError::IoError("标准输入未捕获".into()))
112        }
113    }
114
115    /// 关闭标准输入
116    pub fn close_stdin(&mut self) {
117        self.inner.stdin.take();
118    }
119
120    /// 读取标准输出
121    pub async fn read_stdout(&mut self) -> Result<Vec<u8>, ProcessError> {
122        if let Some(stdout) = self.inner.stdout.as_mut() {
123            let mut buf = Vec::new();
124            stdout.read_to_end(&mut buf).await?;
125            Ok(buf)
126        } else {
127            Err(ProcessError::IoError("标准输出未捕获".into()))
128        }
129    }
130
131    /// 读取标准错误
132    pub async fn read_stderr(&mut self) -> Result<Vec<u8>, ProcessError> {
133        if let Some(stderr) = self.inner.stderr.as_mut() {
134            let mut buf = Vec::new();
135            stderr.read_to_end(&mut buf).await?;
136            Ok(buf)
137        } else {
138            Err(ProcessError::IoError("标准错误未捕获".into()))
139        }
140    }
141
142    /// 发送终止信号(优雅终止)
143    #[cfg(unix)]
144    pub fn terminate(&self) -> Result<(), ProcessError> {
145        use nix::sys::signal::{kill, Signal};
146        use nix::unistd::Pid;
147
148        kill(Pid::from_raw(self.pid as i32), Signal::SIGTERM)
149            .map_err(|e| ProcessError::SignalFailed(e.to_string()))
150    }
151
152    /// 发送终止信号(Windows)
153    #[cfg(windows)]
154    pub fn terminate(&mut self) -> Result<(), ProcessError> {
155        self.inner
156            .start_kill()
157            .map_err(|e| ProcessError::SignalFailed(e.to_string()))
158    }
159
160    /// 强制终止进程
161    pub fn kill(&mut self) -> Result<(), ProcessError> {
162        self.inner
163            .start_kill()
164            .map_err(|e| ProcessError::SignalFailed(e.to_string()))
165    }
166
167    /// 获取底层 tokio Child 的可变引用
168    pub fn inner_mut(&mut self) -> &mut TokioChild {
169        &mut self.inner
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176
177    #[test]
178    fn test_exit_status() {
179        let status = ExitStatus::from_code(0);
180        assert!(status.success());
181        assert_eq!(status.code(), Some(0));
182
183        let status = ExitStatus::from_code(1);
184        assert!(!status.success());
185        assert_eq!(status.code(), Some(1));
186
187        let status = ExitStatus::unknown();
188        assert!(!status.success());
189        assert_eq!(status.code(), None);
190    }
191
192    #[tokio::test]
193    async fn test_simple_command() {
194        use crate::command::Command;
195
196        #[cfg(windows)]
197        let output = Command::new("cmd")
198            .args(["/C", "echo", "hello"])
199            .output()
200            .await
201            .unwrap();
202
203        #[cfg(unix)]
204        let output = Command::new("echo")
205            .arg("hello")
206            .output()
207            .await
208            .unwrap();
209
210        assert!(output.success());
211        assert!(output.stdout_lossy().contains("hello"));
212    }
213}