1pub mod error;
39pub use error::{Error, Result};
40
41pub mod cmdbuilder;
42pub use cmdbuilder::CommandBuilder;
43
44#[cfg(unix)]
45pub mod unix;
46#[cfg(windows)]
47pub mod win;
48
49#[cfg(feature = "serial")]
50pub mod serial;
51
52use downcast_rs::{impl_downcast, Downcast};
53#[cfg(feature = "serde_support")]
54use serde::{Deserialize, Serialize};
55use std::io::Result as IoResult;
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
60pub struct PtySize {
61 pub rows: u16,
63 pub cols: u16,
65 pub pixel_width: u16,
68 pub pixel_height: u16,
71}
72
73impl Default for PtySize {
74 fn default() -> Self {
75 PtySize {
76 rows: 24,
77 cols: 80,
78 pixel_width: 0,
79 pixel_height: 0,
80 }
81 }
82}
83
84pub trait MasterPty: Downcast + Send {
89 fn resize(&self, size: PtySize) -> Result<()>;
91 fn get_size(&self) -> Result<PtySize>;
93 fn try_clone_reader(&self) -> Result<Box<dyn std::io::Read + Send>>;
96 fn take_writer(&self) -> Result<Box<dyn std::io::Write + Send>>;
100
101 fn process_group_leader(&self) -> Option<i32> {
104 None
105 }
106
107 fn as_raw_fd(&self) -> Option<i32> {
110 None
111 }
112
113 fn tty_name(&self) -> Option<std::path::PathBuf> {
116 None
117 }
118}
119impl_downcast!(MasterPty);
120
121#[cfg(unix)]
125pub trait MasterPtyExt {
126 fn get_termios(&self) -> Option<nix::sys::termios::Termios> {
128 None
129 }
130}
131
132pub trait Child: std::fmt::Debug + ChildKiller + Downcast + Send {
134 fn try_wait(&mut self) -> IoResult<Option<ExitStatus>>;
136 fn wait(&mut self) -> IoResult<ExitStatus>;
138 fn process_id(&self) -> Option<u32>;
140 #[cfg(windows)]
142 fn as_raw_handle(&self) -> Option<std::os::windows::io::RawHandle>;
143}
144impl_downcast!(Child);
145
146pub trait ChildKiller: std::fmt::Debug + Downcast + Send {
148 fn kill(&mut self) -> IoResult<()>;
150
151 fn clone_killer(&self) -> Box<dyn ChildKiller + Send + Sync>;
155}
156impl_downcast!(ChildKiller);
157
158pub trait SlavePty: Send {
161 fn spawn_command(&self, cmd: CommandBuilder) -> Result<Box<dyn Child + Send + Sync>>;
163}
164
165#[derive(Debug, Clone)]
167pub struct ExitStatus {
168 code: u32,
169 signal: Option<String>,
170}
171
172impl ExitStatus {
173 pub fn with_exit_code(code: u32) -> Self {
175 Self { code, signal: None }
176 }
177
178 pub fn with_signal(signal: &str) -> Self {
180 Self {
181 code: 1,
182 signal: Some(signal.to_string()),
183 }
184 }
185
186 pub fn success(&self) -> bool {
188 self.signal.is_none() && self.code == 0
189 }
190
191 pub fn exit_code(&self) -> u32 {
193 self.code
194 }
195
196 pub fn signal(&self) -> Option<&str> {
198 self.signal.as_deref()
199 }
200}
201
202impl From<std::process::ExitStatus> for ExitStatus {
203 fn from(status: std::process::ExitStatus) -> ExitStatus {
204 #[cfg(unix)]
205 {
206 use std::os::unix::process::ExitStatusExt;
207
208 if let Some(signal) = status.signal() {
209 let signame = unsafe { libc::strsignal(signal) };
210 let signal = if signame.is_null() {
211 format!("Signal {}", signal)
212 } else {
213 let signame = unsafe { std::ffi::CStr::from_ptr(signame) };
214 signame.to_string_lossy().to_string()
215 };
216
217 return ExitStatus {
218 code: status.code().map(|c| c as u32).unwrap_or(1),
219 signal: Some(signal),
220 };
221 }
222 }
223
224 let code =
225 status
226 .code()
227 .map(|c| c as u32)
228 .unwrap_or_else(|| if status.success() { 0 } else { 1 });
229
230 ExitStatus { code, signal: None }
231 }
232}
233
234impl std::fmt::Display for ExitStatus {
235 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
236 if self.success() {
237 write!(fmt, "Success")
238 } else {
239 match &self.signal {
240 Some(sig) => write!(fmt, "Terminated by {}", sig),
241 None => write!(fmt, "Exited with code {}", self.code),
242 }
243 }
244 }
245}
246
247pub struct PtyPair {
249 pub slave: Box<dyn SlavePty>,
252 pub master: Box<dyn MasterPty + Send>,
253}
254
255pub trait PtySystem: Downcast {
258 fn openpty(&self, size: PtySize) -> Result<PtyPair>;
261}
262impl_downcast!(PtySystem);
263
264impl Child for std::process::Child {
265 fn try_wait(&mut self) -> IoResult<Option<ExitStatus>> {
266 std::process::Child::try_wait(self).map(|s| s.map(Into::into))
267 }
268
269 fn wait(&mut self) -> IoResult<ExitStatus> {
270 std::process::Child::wait(self).map(Into::into)
271 }
272
273 fn process_id(&self) -> Option<u32> {
274 Some(self.id())
275 }
276
277 #[cfg(windows)]
278 fn as_raw_handle(&self) -> Option<std::os::windows::io::RawHandle> {
279 Some(std::os::windows::io::AsRawHandle::as_raw_handle(self))
280 }
281}
282
283#[derive(Debug)]
284struct ProcessSignaller {
285 pid: Option<u32>,
286
287 #[cfg(windows)]
288 handle: Option<filedescriptor::OwnedHandle>,
289}
290
291#[cfg(windows)]
292impl ChildKiller for ProcessSignaller {
293 fn kill(&mut self) -> IoResult<()> {
294 if let Some(handle) = &self.handle {
295 use std::os::windows::io::AsRawHandle;
296 unsafe {
297 if windows_sys::Win32::System::Threading::TerminateProcess(
298 handle.as_raw_handle(),
299 127,
300 ) == 0
301 {
302 return Err(std::io::Error::last_os_error());
303 }
304 }
305 }
306 Ok(())
307 }
308 fn clone_killer(&self) -> Box<dyn ChildKiller + Send + Sync> {
309 Box::new(Self {
310 pid: self.pid,
311 handle: self.handle.as_ref().and_then(|h| h.try_clone().ok()),
312 })
313 }
314}
315
316#[cfg(unix)]
317impl ChildKiller for ProcessSignaller {
318 fn kill(&mut self) -> IoResult<()> {
319 if let Some(pid) = self.pid {
320 let result = unsafe { libc::kill(pid as i32, libc::SIGHUP) };
321 if result != 0 {
322 return Err(std::io::Error::last_os_error());
323 }
324 }
325 Ok(())
326 }
327
328 fn clone_killer(&self) -> Box<dyn ChildKiller + Send + Sync> {
329 Box::new(Self { pid: self.pid })
330 }
331}
332
333impl ChildKiller for std::process::Child {
334 fn kill(&mut self) -> IoResult<()> {
335 #[cfg(unix)]
336 {
337 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 {
343 if attempt > 0 {
344 std::thread::sleep(std::time::Duration::from_millis(50));
345 }
346 if let Ok(Some(_)) = self.try_wait() {
347 return Ok(());
348 }
349 }
350 }
351
352 std::process::Child::kill(self)
353 }
354
355 #[cfg(windows)]
356 fn clone_killer(&self) -> Box<dyn ChildKiller + Send + Sync> {
357 use std::os::windows::io::AsRawHandle;
358 struct RawDup(std::os::windows::io::RawHandle);
359 impl AsRawHandle for RawDup {
360 fn as_raw_handle(&self) -> std::os::windows::io::RawHandle {
361 self.0
362 }
363 }
364
365 Box::new(ProcessSignaller {
366 pid: self.process_id(),
367 handle: Child::as_raw_handle(self)
368 .as_ref()
369 .and_then(|h| filedescriptor::OwnedHandle::dup(&RawDup(*h)).ok()),
370 })
371 }
372
373 #[cfg(unix)]
374 fn clone_killer(&self) -> Box<dyn ChildKiller + Send + Sync> {
375 Box::new(ProcessSignaller {
376 pid: self.process_id(),
377 })
378 }
379}
380
381pub fn native_pty_system() -> NativePtySystem {
386 NativePtySystem::default()
387}
388
389pub fn native_pty_system_boxed() -> Box<dyn PtySystem + Send> {
394 Box::new(NativePtySystem::default())
395}
396
397#[cfg(unix)]
398pub type NativePtySystem = unix::UnixPtySystem;
399#[cfg(windows)]
400pub type NativePtySystem = win::conpty::ConPtySystem;