1use std::ffi::OsString;
2#[cfg(unix)]
3use std::fs::File;
4#[cfg(unix)]
5use std::os::unix::process::CommandExt;
6use std::path::PathBuf;
7use std::process::ExitStatus;
8#[cfg(unix)]
9use std::process::{Child, Command, Stdio};
10
11#[cfg(all(not(unix), not(windows)))]
12use crate::unsupported_op;
13#[cfg(all(not(unix), not(windows)))]
14use crate::PtyError;
15#[cfg(any(unix, windows))]
16use crate::{backend, PtyPair};
17use crate::{ProcessId, PtyMaster, Result, Signal, TerminalSize};
18
19#[derive(Clone, Debug)]
21#[cfg_attr(not(unix), allow(dead_code))]
22pub struct ChildCommand {
23 pub(crate) program: PathBuf,
24 pub(crate) arg0: Option<OsString>,
25 pub(crate) args: Vec<OsString>,
26 pub(crate) env: Vec<(OsString, OsString)>,
27 pub(crate) clear_env: bool,
28 pub(crate) current_dir: Option<PathBuf>,
29 pub(crate) size: Option<TerminalSize>,
30}
31
32impl ChildCommand {
33 #[must_use]
35 pub fn new(program: impl Into<PathBuf>) -> Self {
36 Self {
37 program: program.into(),
38 arg0: None,
39 args: Vec::new(),
40 env: Vec::new(),
41 clear_env: false,
42 current_dir: None,
43 size: None,
44 }
45 }
46
47 #[must_use]
49 pub fn arg0(mut self, arg0: impl Into<OsString>) -> Self {
50 self.arg0 = Some(arg0.into());
51 self
52 }
53
54 #[must_use]
56 pub fn arg(mut self, arg: impl Into<OsString>) -> Self {
57 self.args.push(arg.into());
58 self
59 }
60
61 #[must_use]
63 pub fn args<I, S>(mut self, args: I) -> Self
64 where
65 I: IntoIterator<Item = S>,
66 S: Into<OsString>,
67 {
68 self.args.extend(args.into_iter().map(Into::into));
69 self
70 }
71
72 #[must_use]
74 pub fn env(mut self, key: impl Into<OsString>, value: impl Into<OsString>) -> Self {
75 self.env.push((key.into(), value.into()));
76 self
77 }
78
79 #[must_use]
81 pub fn clear_env(mut self) -> Self {
82 self.clear_env = true;
83 self
84 }
85
86 #[must_use]
88 pub fn current_dir(mut self, path: impl Into<PathBuf>) -> Self {
89 self.current_dir = Some(path.into());
90 self
91 }
92
93 #[must_use]
95 pub fn size(mut self, size: TerminalSize) -> Self {
96 self.size = Some(size);
97 self
98 }
99
100 pub fn spawn(self) -> Result<SpawnedPty> {
102 spawn_child(self)
103 }
104}
105
106#[derive(Debug)]
108pub struct SpawnedPty {
109 master: PtyMaster,
110 child: PtyChild,
111}
112
113impl SpawnedPty {
114 #[must_use]
116 pub fn master(&self) -> &PtyMaster {
117 &self.master
118 }
119
120 #[must_use]
122 pub fn child(&self) -> &PtyChild {
123 &self.child
124 }
125
126 #[must_use]
128 pub fn child_mut(&mut self) -> &mut PtyChild {
129 &mut self.child
130 }
131
132 #[must_use]
134 pub fn into_parts(self) -> (PtyMaster, PtyChild) {
135 (self.master, self.child)
136 }
137}
138
139#[derive(Debug)]
141pub struct PtyChild {
142 #[cfg(unix)]
143 child: Child,
144 #[cfg(windows)]
145 child: backend::WindowsChild,
146 pid: ProcessId,
147}
148
149impl PtyChild {
150 #[must_use]
156 pub fn pid(&self) -> ProcessId {
157 self.pid
158 }
159
160 pub fn wait(&mut self) -> Result<ExitStatus> {
162 #[cfg(unix)]
163 {
164 Ok(self.child.wait()?)
165 }
166
167 #[cfg(not(unix))]
168 {
169 #[cfg(windows)]
170 {
171 backend::wait_child(&mut self.child)
172 }
173
174 #[cfg(not(windows))]
175 {
176 Err(PtyError::Unsupported(unsupported_op::WAIT_FOR_PTY_CHILD))
177 }
178 }
179 }
180
181 pub fn try_wait(&mut self) -> Result<Option<ExitStatus>> {
183 #[cfg(unix)]
184 {
185 Ok(self.child.try_wait()?)
186 }
187
188 #[cfg(not(unix))]
189 {
190 #[cfg(windows)]
191 {
192 backend::try_wait_child(&mut self.child)
193 }
194
195 #[cfg(not(windows))]
196 {
197 Err(PtyError::Unsupported(
198 unsupported_op::TRY_WAIT_FOR_PTY_CHILD,
199 ))
200 }
201 }
202 }
203
204 #[cfg(windows)]
206 pub fn try_clone_for_wait(&self) -> Result<Self> {
207 Ok(Self {
208 child: backend::try_clone_child_for_wait(&self.child)?,
209 pid: self.pid,
210 })
211 }
212
213 #[cfg(windows)]
220 pub fn close_pseudoconsole(&self) {
221 backend::close_child_pseudoconsole(&self.child);
222 }
223
224 pub fn interrupt(&self) -> Result<()> {
226 self.kill(Signal::INT)
227 }
228
229 pub fn terminate_forcefully(&self) -> Result<()> {
231 self.kill(Signal::KILL)
232 }
233
234 pub fn kill(&self, signal: Signal) -> Result<()> {
241 #[cfg(unix)]
242 {
243 backend::kill_foreground_process_group(self.pid, signal)
244 }
245
246 #[cfg(not(unix))]
247 {
248 #[cfg(windows)]
249 {
250 backend::kill_child(&self.child, signal)
251 }
252
253 #[cfg(not(windows))]
254 {
255 let _ = signal;
256 Err(PtyError::Unsupported(unsupported_op::SIGNAL_PTY_FOREGROUND))
257 }
258 }
259 }
260
261 pub fn kill_session_leader(&self, signal: Signal) -> Result<()> {
267 #[cfg(unix)]
268 {
269 backend::kill_process(self.pid, signal)?;
270 Ok(())
271 }
272
273 #[cfg(not(unix))]
274 {
275 #[cfg(windows)]
276 {
277 backend::kill_child(&self.child, signal)
278 }
279
280 #[cfg(not(windows))]
281 {
282 let _ = signal;
283 Err(PtyError::Unsupported(
284 unsupported_op::SIGNAL_PTY_SESSION_LEADER,
285 ))
286 }
287 }
288 }
289
290 #[cfg(unix)]
297 pub fn continue_if_stopped(&self) -> Result<bool> {
298 let Some(stop_signal) = backend::stopped_signal(self.pid)? else {
299 return Ok(false);
300 };
301 if stop_signal == libc::SIGTTIN || stop_signal == libc::SIGTTOU {
302 return Ok(false);
303 }
304
305 backend::kill_foreground_process_group(self.pid, Signal::CONT)
306 .or_else(|_| backend::kill_process(self.pid, Signal::CONT))?;
307 Ok(true)
308 }
309}
310
311#[cfg(unix)]
312fn spawn_child(command: ChildCommand) -> Result<SpawnedPty> {
313 let pair = match command.size {
314 Some(size) => PtyPair::open_with_size(size)?,
315 None => PtyPair::open()?,
316 };
317 let (master, slave) = pair.into_split();
318 let raw_master_fd = master.raw_fd();
319
320 let stdin = File::from(slave.try_clone()?.into_owned_fd());
321 let stdout = File::from(slave.try_clone()?.into_owned_fd());
322 let stderr = File::from(slave.into_owned_fd());
323
324 let mut std_command = Command::new(&command.program);
325 if let Some(arg0) = &command.arg0 {
326 std_command.arg0(arg0);
327 }
328 std_command.args(&command.args);
329 std_command.stdin(Stdio::from(stdin));
330 std_command.stdout(Stdio::from(stdout));
331 std_command.stderr(Stdio::from(stderr));
332 if command.clear_env {
333 std_command.env_clear();
334 }
335 if let Some(current_dir) = &command.current_dir {
336 std_command.current_dir(current_dir);
337 }
338
339 for (key, value) in &command.env {
340 std_command.env(key, value);
341 }
342
343 let pre_exec = move || {
344 rmux_os::signals::reset_child_signal_dispositions()?;
345 backend::setup_child_controlling_terminal(raw_master_fd)
346 };
347
348 unsafe {
354 std_command.pre_exec(pre_exec);
355 }
356
357 let child = std_command.spawn()?;
358 let pid = ProcessId::new(child.id())?;
359
360 Ok(SpawnedPty {
361 master,
362 child: PtyChild { child, pid },
363 })
364}
365
366#[cfg(not(unix))]
367fn spawn_child(_command: ChildCommand) -> Result<SpawnedPty> {
368 #[cfg(windows)]
369 {
370 let pair = match _command.size {
371 Some(size) => PtyPair::open_with_size(size)?,
372 None => PtyPair::open()?,
373 };
374 let master = pair.into_master();
375 let child = backend::spawn_child(_command, master.windows_pty())?;
376 let pid = child.pid();
377 Ok(SpawnedPty {
378 master,
379 child: PtyChild { child, pid },
380 })
381 }
382
383 #[cfg(not(windows))]
384 {
385 Err(PtyError::Unsupported(unsupported_op::SPAWN_PTY_CHILD))
386 }
387}