1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-28 07:55:03 +03:00
wezterm/pty/src/lib.rs
Wez Furlong 3f6ff534d3 windows: fix lingering cmd.exe panes
Since removing the regular periodic background tasks, we're now
prone to not noticing child processes exiting.

This commit explicictly schedules a thread to do that on Windows
so that we can close a tab as soon as it exits.
2021-05-28 15:11:29 -07:00

254 lines
8.5 KiB
Rust

//! This crate provides a cross platform API for working with the
//! psuedo terminal (pty) interfaces provided by the system.
//! Unlike other crates in this space, this crate provides a set
//! of traits that allow selecting from different implementations
//! at runtime.
//! This crate is part of [wezterm](https://github.com/wez/wezterm).
//!
//! ```no_run
//! use portable_pty::{CommandBuilder, PtySize, native_pty_system, PtySystem};
//! use anyhow::Error;
//!
//! // Use the native pty implementation for the system
//! let pty_system = native_pty_system();
//!
//! // Create a new pty
//! let mut pair = pty_system.openpty(PtySize {
//! rows: 24,
//! cols: 80,
//! // Not all systems support pixel_width, pixel_height,
//! // but it is good practice to set it to something
//! // that matches the size of the selected font. That
//! // is more complex than can be shown here in this
//! // brief example though!
//! pixel_width: 0,
//! pixel_height: 0,
//! })?;
//!
//! // Spawn a shell into the pty
//! let cmd = CommandBuilder::new("bash");
//! let child = pair.slave.spawn_command(cmd)?;
//!
//! // Read and parse output from the pty with reader
//! let mut reader = pair.master.try_clone_reader()?;
//!
//! // Send data to the pty by writing to the master
//! writeln!(pair.master, "ls -l\r\n")?;
//! # Ok::<(), Error>(())
//! ```
//!
//! ## ssh2
//!
//! If the `ssh` feature is enabled, this crate exposes an
//! `ssh::SshSession` type that can wrap an established ssh
//! session with an implementation of `PtySystem`, allowing
//! you to use the same pty interface with remote ptys.
use anyhow::Error;
#[cfg(unix)]
use libc;
#[cfg(feature = "serde_support")]
use serde_derive::*;
use std::io::Result as IoResult;
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;
/// Represents the size of the visible display area in the pty
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub struct PtySize {
/// The number of lines of text
pub rows: u16,
/// The number of columns of text
pub cols: u16,
/// The width of a cell in pixels. Note that some systems never
/// fill this value and ignore it.
pub pixel_width: u16,
/// The height of a cell in pixels. Note that some systems never
/// fill this value and ignore it.
pub pixel_height: u16,
}
impl Default for PtySize {
fn default() -> Self {
PtySize {
rows: 24,
cols: 80,
pixel_width: 0,
pixel_height: 0,
}
}
}
/// Represents the master/control end of the pty
pub trait MasterPty: std::io::Write {
/// Inform the kernel and thus the child process that the window resized.
/// It will update the winsize information maintained by the kernel,
/// and generate a signal for the child to notice and update its state.
fn resize(&self, size: PtySize) -> Result<(), Error>;
/// Retrieves the size of the pty as known by the kernel
fn get_size(&self) -> Result<PtySize, Error>;
/// Obtain a readable handle; output from the slave(s) is readable
/// via this stream.
fn try_clone_reader(&self) -> Result<Box<dyn std::io::Read + Send>, Error>;
/// Obtain a writable handle; writing to it will send data to the
/// slave end. This is equivalent to the Write impl on MasterPty
/// itself, but allows splitting it off into a separate object.
fn try_clone_writer(&self) -> Result<Box<dyn std::io::Write + Send>, Error>;
/// If applicable to the type of the tty, return the local process id
/// of the process group or session leader
#[cfg(unix)]
fn process_group_leader(&self) -> Option<libc::pid_t>;
}
/// Represents a child process spawned into the pty.
/// This handle can be used to wait for or terminate that child process.
pub trait Child: std::fmt::Debug {
/// Poll the child to see if it has completed.
/// Does not block.
/// Returns None if the child has not yet terminated,
/// else returns its exit status.
fn try_wait(&mut self) -> IoResult<Option<ExitStatus>>;
/// Terminate the child process
fn kill(&mut self) -> IoResult<()>;
/// Blocks execution until the child process has completed,
/// yielding its exit status.
fn wait(&mut self) -> IoResult<ExitStatus>;
/// Returns the process identifier of the child process,
/// if applicable
fn process_id(&self) -> Option<u32>;
/// Returns the process handle of the child process, if applicable.
/// Only available on Windows.
#[cfg(windows)]
fn as_raw_handle(&self) -> Option<std::os::windows::io::RawHandle>;
}
/// Represents the slave side of a pty.
/// Can be used to spawn processes into the pty.
pub trait SlavePty {
/// Spawns the command specified by the provided CommandBuilder
fn spawn_command(&self, cmd: CommandBuilder) -> Result<Box<dyn Child + Send + Sync>, Error>;
}
/// Represents the exit status of a child process.
/// This is rather anemic in the current version of this crate,
/// holding only an indicator of success or failure.
#[derive(Debug, Clone)]
pub struct ExitStatus {
successful: bool,
}
impl ExitStatus {
/// Construct an ExitStatus from a process return code
pub fn with_exit_code(code: u32) -> Self {
Self {
successful: code == 0,
}
}
pub fn success(&self) -> bool {
self.successful
}
}
impl From<std::process::ExitStatus> for ExitStatus {
fn from(status: std::process::ExitStatus) -> ExitStatus {
ExitStatus {
successful: status.success(),
}
}
}
pub struct PtyPair {
// slave is listed first so that it is dropped first.
// The drop order is stable and specified by rust rfc 1857
pub slave: Box<dyn SlavePty + Send>,
pub master: Box<dyn MasterPty + Send>,
}
/// The `PtySystem` trait allows an application to work with multiple
/// possible Pty implementations at runtime. This is important on
/// Windows systems which have a variety of implementations.
pub trait PtySystem {
/// Create a new Pty instance with the window size set to the specified
/// dimensions. Returns a (master, slave) Pty pair. The master side
/// is used to drive the slave side.
fn openpty(&self, size: PtySize) -> anyhow::Result<PtyPair>;
}
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 kill(&mut self) -> IoResult<()> {
#[cfg(unix)]
{
// On unix, we send the SIGHUP signal instead of trying to kill
// the process. The default behavior of a process receiving this
// signal is to be killed unless it configured a signal handler.
let result = unsafe { libc::kill(self.id() as i32, libc::SIGHUP) };
if result != 0 {
return Err(std::io::Error::last_os_error());
}
// We successfully delivered SIGHUP, but the semantics of Child::kill
// are that on success the process is dead or shortly about to
// terminate. Since SIGUP doesn't guarantee termination, we
// give the process a bit of a grace period to shutdown or do whatever
// it is doing in its signal handler befre we proceed with the
// full on kill.
for attempt in 0..5 {
if attempt > 0 {
std::thread::sleep(std::time::Duration::from_millis(50));
}
if let Ok(Some(_)) = self.try_wait() {
// It completed, so report success!
return Ok(());
}
}
// it's still alive after a grace period, so proceed with a kill
}
std::process::Child::kill(self)
}
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))
}
}
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;