use anyhow::Error;
use downcast_rs::{impl_downcast, Downcast};
#[cfg(unix)]
use libc;
#[cfg(feature = "serde_support")]
use serde_derive::*;
use std::io::Result as IoResult;
#[cfg(windows)]
use std::os::windows::prelude::{AsRawHandle, RawHandle};
pub mod cmdbuilder;
pub use cmdbuilder::CommandBuilder;
#[cfg(unix)]
pub mod unix;
#[cfg(windows)]
pub mod win;
#[cfg(feature = "ssh")]
pub mod ssh;
pub mod serial;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub struct PtySize {
pub rows: u16,
pub cols: u16,
pub pixel_width: u16,
pub pixel_height: u16,
}
impl Default for PtySize {
fn default() -> Self {
PtySize {
rows: 24,
cols: 80,
pixel_width: 0,
pixel_height: 0,
}
}
}
pub trait MasterPty: std::io::Write {
fn resize(&self, size: PtySize) -> Result<(), Error>;
fn get_size(&self) -> Result<PtySize, Error>;
fn try_clone_reader(&self) -> Result<Box<dyn std::io::Read + Send>, Error>;
fn try_clone_writer(&self) -> Result<Box<dyn std::io::Write + Send>, Error>;
#[cfg(unix)]
fn process_group_leader(&self) -> Option<libc::pid_t>;
}
pub trait Child: std::fmt::Debug + ChildKiller {
fn try_wait(&mut self) -> IoResult<Option<ExitStatus>>;
fn wait(&mut self) -> IoResult<ExitStatus>;
fn process_id(&self) -> Option<u32>;
#[cfg(windows)]
fn as_raw_handle(&self) -> Option<std::os::windows::io::RawHandle>;
}
pub trait ChildKiller: std::fmt::Debug {
fn kill(&mut self) -> IoResult<()>;
fn clone_killer(&self) -> Box<dyn ChildKiller + Send + Sync>;
}
pub trait SlavePty {
fn spawn_command(
&self,
cmd: CommandBuilder,
) -> Result<Box<dyn Child + Send + Sync>, Error>;
}
#[derive(Debug, Clone)]
pub struct ExitStatus {
code: u32,
signal: Option<String>,
}
impl ExitStatus {
pub fn with_exit_code(code: u32) -> Self {
Self { code, signal: None }
}
pub fn with_signal(signal: &str) -> Self {
Self {
code: 1,
signal: Some(signal.to_string()),
}
}
pub fn success(&self) -> bool {
match self.signal {
None => self.code == 0,
Some(_) => false,
}
}
pub fn exit_code(&self) -> u32 {
self.code
}
}
impl From<std::process::ExitStatus> for ExitStatus {
fn from(status: std::process::ExitStatus) -> ExitStatus {
#[cfg(unix)]
{
use std::os::unix::process::ExitStatusExt;
if let Some(signal) = status.signal() {
let signame = unsafe { libc::strsignal(signal) };
let signal = if signame.is_null() {
format!("Signal {}", signal)
} else {
let signame = unsafe { std::ffi::CStr::from_ptr(signame) };
signame.to_string_lossy().to_string()
};
return ExitStatus {
code: status.code().map(|c| c as u32).unwrap_or(1),
signal: Some(signal),
};
}
}
let code = status.code().map(|c| c as u32).unwrap_or_else(|| {
if status.success() {
0
} else {
1
}
});
ExitStatus { code, signal: None }
}
}
impl std::fmt::Display for ExitStatus {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
if self.success() {
write!(fmt, "Success")
} else {
match &self.signal {
Some(sig) => write!(fmt, "Terminated by {}", sig),
None => write!(fmt, "Exited with code {}", self.code),
}
}
}
}
pub struct PtyPair {
pub slave: Box<dyn SlavePty + Send>,
pub master: Box<dyn MasterPty + Send>,
}
pub trait PtySystem: Downcast {
fn openpty(&self, size: PtySize) -> anyhow::Result<PtyPair>;
}
impl_downcast!(PtySystem);
impl Child for std::process::Child {
fn try_wait(&mut self) -> IoResult<Option<ExitStatus>> {
std::process::Child::try_wait(self).map(|s| match s {
Some(s) => Some(s.into()),
None => None,
})
}
fn wait(&mut self) -> IoResult<ExitStatus> {
std::process::Child::wait(self).map(Into::into)
}
fn process_id(&self) -> Option<u32> {
Some(self.id())
}
#[cfg(windows)]
fn as_raw_handle(&self) -> Option<std::os::windows::io::RawHandle> {
Some(std::os::windows::io::AsRawHandle::as_raw_handle(self))
}
}
#[derive(Debug)]
struct ProcessSignaller {
pid: Option<u32>,
#[cfg(windows)]
handle: Option<filedescriptor::OwnedHandle>,
}
#[cfg(windows)]
impl ChildKiller for ProcessSignaller {
fn kill(&mut self) -> IoResult<()> {
if let Some(handle) = &self.handle {
unsafe {
if winapi::um::processthreadsapi::TerminateProcess(
handle.as_raw_handle() as _,
127,
) == 0
{
return Err(std::io::Error::last_os_error());
}
}
}
Ok(())
}
fn clone_killer(&self) -> Box<dyn ChildKiller + Send + Sync> {
Box::new(Self {
pid: self.pid,
handle: self.handle.as_ref().and_then(|h| h.try_clone().ok()),
})
}
}
#[cfg(unix)]
impl ChildKiller for ProcessSignaller {
fn kill(&mut self) -> IoResult<()> {
if let Some(pid) = self.pid {
let result = unsafe { libc::kill(pid as i32, libc::SIGHUP) };
if result != 0 {
return Err(std::io::Error::last_os_error());
}
}
Ok(())
}
fn clone_killer(&self) -> Box<dyn ChildKiller + Send + Sync> {
Box::new(Self { pid: self.pid })
}
}
impl ChildKiller for std::process::Child {
fn kill(&mut self) -> IoResult<()> {
#[cfg(unix)]
{
let result = unsafe { libc::kill(self.id() as i32, libc::SIGHUP) };
if result != 0 {
return Err(std::io::Error::last_os_error());
}
for attempt in 0..5 {
if attempt > 0 {
std::thread::sleep(std::time::Duration::from_millis(50));
}
if let Ok(Some(_)) = self.try_wait() {
return Ok(());
}
}
}
std::process::Child::kill(self)
}
#[cfg(windows)]
fn clone_killer(&self) -> Box<dyn ChildKiller + Send + Sync> {
struct RawDup(RawHandle);
impl AsRawHandle for RawDup {
fn as_raw_handle(&self) -> RawHandle {
self.0
}
}
Box::new(ProcessSignaller {
pid: self.process_id(),
handle: Child::as_raw_handle(self)
.as_ref()
.and_then(|h| filedescriptor::OwnedHandle::dup(&RawDup(*h)).ok()),
})
}
#[cfg(unix)]
fn clone_killer(&self) -> Box<dyn ChildKiller + Send + Sync> {
Box::new(ProcessSignaller {
pid: self.process_id(),
})
}
}
pub fn native_pty_system() -> Box<dyn PtySystem> {
Box::new(NativePtySystem::default())
}
#[cfg(unix)]
pub type NativePtySystem = unix::UnixPtySystem;
#[cfg(windows)]
pub type NativePtySystem = win::conpty::ConPtySystem;