1use anyhow::Error;
47use downcast_rs::{impl_downcast, Downcast};
48#[cfg(unix)]
49use libc;
50#[cfg(feature = "serde_support")]
51use serde_derive::*;
52use std::io::Result as IoResult;
53#[cfg(windows)]
54use std::os::windows::prelude::{AsRawHandle, RawHandle};
55
56pub mod cmdbuilder;
57pub use cmdbuilder::CommandBuilder;
58
59#[cfg(unix)]
60pub mod unix;
61#[cfg(windows)]
62pub mod win;
63
64#[cfg(feature = "ssh")]
65pub mod ssh;
66
67pub mod serial;
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
72pub struct PtySize {
73 pub rows: u16,
75 pub cols: u16,
77 pub pixel_width: u16,
80 pub pixel_height: u16,
83}
84
85impl Default for PtySize {
86 fn default() -> Self {
87 PtySize {
88 rows: 24,
89 cols: 80,
90 pixel_width: 0,
91 pixel_height: 0,
92 }
93 }
94}
95
96pub trait MasterPty: std::io::Write {
98 fn resize(&self, size: PtySize) -> Result<(), Error>;
102 fn get_size(&self) -> Result<PtySize, Error>;
104 fn try_clone_reader(&self) -> Result<Box<dyn std::io::Read + Send>, Error>;
107 fn try_clone_writer(&self) -> Result<Box<dyn std::io::Write + Send>, Error>;
111
112 #[cfg(unix)]
115 fn process_group_leader(&self) -> Option<libc::pid_t>;
116}
117
118pub trait Child: std::fmt::Debug + ChildKiller {
121 fn try_wait(&mut self) -> IoResult<Option<ExitStatus>>;
126 fn wait(&mut self) -> IoResult<ExitStatus>;
129 fn process_id(&self) -> Option<u32>;
132 #[cfg(windows)]
135 fn as_raw_handle(&self) -> Option<std::os::windows::io::RawHandle>;
136}
137
138pub trait ChildKiller: std::fmt::Debug {
140 fn kill(&mut self) -> IoResult<()>;
142
143 fn clone_killer(&self) -> Box<dyn ChildKiller + Send + Sync>;
147}
148
149pub trait SlavePty {
152 fn spawn_command(
154 &self,
155 cmd: CommandBuilder,
156 ) -> Result<Box<dyn Child + Send + Sync>, Error>;
157}
158
159#[derive(Debug, Clone)]
161pub struct ExitStatus {
162 code: u32,
163 signal: Option<String>,
164}
165
166impl ExitStatus {
167 pub fn with_exit_code(code: u32) -> Self {
169 Self { code, signal: None }
170 }
171
172 pub fn with_signal(signal: &str) -> Self {
174 Self {
175 code: 1,
176 signal: Some(signal.to_string()),
177 }
178 }
179
180 pub fn success(&self) -> bool {
182 match self.signal {
183 None => self.code == 0,
184 Some(_) => false,
185 }
186 }
187
188 pub fn exit_code(&self) -> u32 {
190 self.code
191 }
192}
193
194impl From<std::process::ExitStatus> for ExitStatus {
195 fn from(status: std::process::ExitStatus) -> ExitStatus {
196 #[cfg(unix)]
197 {
198 use std::os::unix::process::ExitStatusExt;
199
200 if let Some(signal) = status.signal() {
201 let signame = unsafe { libc::strsignal(signal) };
202 let signal = if signame.is_null() {
203 format!("Signal {}", signal)
204 } else {
205 let signame = unsafe { std::ffi::CStr::from_ptr(signame) };
206 signame.to_string_lossy().to_string()
207 };
208
209 return ExitStatus {
210 code: status.code().map(|c| c as u32).unwrap_or(1),
211 signal: Some(signal),
212 };
213 }
214 }
215
216 let code = status.code().map(|c| c as u32).unwrap_or_else(|| {
217 if status.success() {
218 0
219 } else {
220 1
221 }
222 });
223
224 ExitStatus { code, signal: None }
225 }
226}
227
228impl std::fmt::Display for ExitStatus {
229 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
230 if self.success() {
231 write!(fmt, "Success")
232 } else {
233 match &self.signal {
234 Some(sig) => write!(fmt, "Terminated by {}", sig),
235 None => write!(fmt, "Exited with code {}", self.code),
236 }
237 }
238 }
239}
240
241pub struct PtyPair {
242 pub slave: Box<dyn SlavePty + Send>,
245 pub master: Box<dyn MasterPty + Send>,
246}
247
248pub trait PtySystem: Downcast {
252 fn openpty(&self, size: PtySize) -> anyhow::Result<PtyPair>;
256}
257impl_downcast!(PtySystem);
258
259impl Child for std::process::Child {
260 fn try_wait(&mut self) -> IoResult<Option<ExitStatus>> {
261 std::process::Child::try_wait(self).map(|s| match s {
262 Some(s) => Some(s.into()),
263 None => None,
264 })
265 }
266
267 fn wait(&mut self) -> IoResult<ExitStatus> {
268 std::process::Child::wait(self).map(Into::into)
269 }
270
271 fn process_id(&self) -> Option<u32> {
272 Some(self.id())
273 }
274
275 #[cfg(windows)]
276 fn as_raw_handle(&self) -> Option<std::os::windows::io::RawHandle> {
277 Some(std::os::windows::io::AsRawHandle::as_raw_handle(self))
278 }
279}
280
281#[derive(Debug)]
282struct ProcessSignaller {
283 pid: Option<u32>,
284
285 #[cfg(windows)]
286 handle: Option<filedescriptor::OwnedHandle>,
287}
288
289#[cfg(windows)]
290impl ChildKiller for ProcessSignaller {
291 fn kill(&mut self) -> IoResult<()> {
292 if let Some(handle) = &self.handle {
293 unsafe {
294 if winapi::um::processthreadsapi::TerminateProcess(
295 handle.as_raw_handle() as _,
296 127,
297 ) == 0
298 {
299 return Err(std::io::Error::last_os_error());
300 }
301 }
302 }
303 Ok(())
304 }
305 fn clone_killer(&self) -> Box<dyn ChildKiller + Send + Sync> {
306 Box::new(Self {
307 pid: self.pid,
308 handle: self.handle.as_ref().and_then(|h| h.try_clone().ok()),
309 })
310 }
311}
312
313#[cfg(unix)]
314impl ChildKiller for ProcessSignaller {
315 fn kill(&mut self) -> IoResult<()> {
316 if let Some(pid) = self.pid {
317 let result = unsafe { libc::kill(pid as i32, libc::SIGHUP) };
318 if result != 0 {
319 return Err(std::io::Error::last_os_error());
320 }
321 }
322 Ok(())
323 }
324
325 fn clone_killer(&self) -> Box<dyn ChildKiller + Send + Sync> {
326 Box::new(Self { pid: self.pid })
327 }
328}
329
330impl ChildKiller for std::process::Child {
331 fn kill(&mut self) -> IoResult<()> {
332 #[cfg(unix)]
333 {
334 let result = unsafe { libc::kill(self.id() as i32, libc::SIGHUP) };
338 if result != 0 {
339 return Err(std::io::Error::last_os_error());
340 }
341
342 for attempt in 0..5 {
349 if attempt > 0 {
350 std::thread::sleep(std::time::Duration::from_millis(50));
351 }
352
353 if let Ok(Some(_)) = self.try_wait() {
354 return Ok(());
356 }
357 }
358
359 }
361
362 std::process::Child::kill(self)
363 }
364
365 #[cfg(windows)]
366 fn clone_killer(&self) -> Box<dyn ChildKiller + Send + Sync> {
367 struct RawDup(RawHandle);
368 impl AsRawHandle for RawDup {
369 fn as_raw_handle(&self) -> RawHandle {
370 self.0
371 }
372 }
373
374 Box::new(ProcessSignaller {
375 pid: self.process_id(),
376 handle: Child::as_raw_handle(self)
377 .as_ref()
378 .and_then(|h| filedescriptor::OwnedHandle::dup(&RawDup(*h)).ok()),
379 })
380 }
381
382 #[cfg(unix)]
383 fn clone_killer(&self) -> Box<dyn ChildKiller + Send + Sync> {
384 Box::new(ProcessSignaller {
385 pid: self.process_id(),
386 })
387 }
388}
389
390pub fn native_pty_system() -> Box<dyn PtySystem> {
391 Box::new(NativePtySystem::default())
392}
393
394#[cfg(unix)]
395pub type NativePtySystem = unix::UnixPtySystem;
396#[cfg(windows)]
397pub type NativePtySystem = win::conpty::ConPtySystem;