#[cfg(feature = "pty")]
#[path = "terminal/conpty_passthrough/mod.rs"]
mod conpty_passthrough;
#[cfg(feature = "pty")]
use crate::platform::terminal::{
PtyBackend, PtyChild, PtyMaster, PtySize, PtySlave,
};
#[cfg(feature = "pty")]
use std::ffi::OsString;
#[cfg(feature = "pty")]
use std::io::{self, Read, Write};
#[cfg(feature = "pty")]
use std::path::Path;
#[cfg(feature = "pty")]
pub use conpty_passthrough::conpty_api::{current_backend_kind, ConPtyBackendKind};
#[cfg(feature = "pty")]
pub struct ConPtyBackend;
#[cfg(feature = "pty")]
impl PtyBackend for ConPtyBackend {
type Master = conpty_passthrough::ConPtyMaster;
type Slave = conpty_passthrough::ConPtySlave;
fn openpty(size: PtySize) -> io::Result<(Self::Master, Self::Slave)> {
let pair = conpty_passthrough::openpty(conpty_passthrough::PtySize {
rows: size.rows,
cols: size.cols,
pixel_width: size.pixel_width,
pixel_height: size.pixel_height,
})?;
Ok((pair.master, pair.slave))
}
}
#[cfg(feature = "pty")]
impl PtyMaster for conpty_passthrough::ConPtyMaster {
fn try_clone_reader(&mut self) -> io::Result<Box<dyn Read + Send>> {
conpty_passthrough::ConPtyMaster::try_clone_reader(self)
}
fn take_writer(&mut self) -> io::Result<Box<dyn Write + Send>> {
conpty_passthrough::ConPtyMaster::take_writer(self)
}
fn resize(&self, size: PtySize) -> io::Result<()> {
conpty_passthrough::ConPtyMaster::resize(
self,
conpty_passthrough::PtySize {
rows: size.rows,
cols: size.cols,
pixel_width: size.pixel_width,
pixel_height: size.pixel_height,
},
)
}
fn get_size(&self) -> io::Result<PtySize> {
let size = conpty_passthrough::ConPtyMaster::get_size(self);
Ok(PtySize {
rows: size.rows,
cols: size.cols,
pixel_width: size.pixel_width,
pixel_height: size.pixel_height,
})
}
}
#[cfg(feature = "pty")]
impl PtySlave for conpty_passthrough::ConPtySlave {
type Child = conpty_passthrough::child::ConPtyChild;
fn spawn(
self,
argv: &[OsString],
cwd: Option<&Path>,
env: Option<&[(OsString, OsString)]>,
) -> io::Result<Self::Child> {
conpty_passthrough::ConPtySlave::spawn(self, argv, cwd, env)
}
}
#[cfg(feature = "pty")]
impl PtyChild for conpty_passthrough::child::ConPtyChild {
fn pid(&self) -> u32 {
conpty_passthrough::child::ConPtyChild::pid(self)
}
fn try_wait(&mut self) -> io::Result<Option<u32>> {
conpty_passthrough::child::ConPtyChild::try_wait(self)
}
fn wait(&mut self) -> io::Result<u32> {
conpty_passthrough::child::ConPtyChild::wait(self)
}
fn kill(&mut self) -> io::Result<()> {
conpty_passthrough::child::ConPtyChild::kill(self)
}
fn as_raw_handle(&self) -> Option<*mut std::ffi::c_void> {
Some(conpty_passthrough::child::ConPtyChild::as_raw_handle(self).cast())
}
fn prepare_process(
&self,
context: PtySpawnContext,
nice: Option<i32>,
) -> std::io::Result<PtyProcessGuard> {
prepare_conpty_child(
context,
conpty_passthrough::child::ConPtyChild::as_raw_handle(self).cast(),
nice,
)
}
}
#[cfg(feature = "pty")]
pub type Backend = ConPtyBackend;
#[cfg(feature = "pty")]
use crate::platform::terminal::PtyInputChunk;
#[cfg(feature = "pty")]
pub struct PtySpawnContext(Vec<u32>);
#[cfg(feature = "pty")]
pub struct PtyProcessGuard(usize);
#[cfg(feature = "pty")]
impl PtyProcessGuard {
pub fn assign_pid(&self, pid: u32) -> std::io::Result<()> {
use winapi::um::handleapi::CloseHandle;
use winapi::um::processthreadsapi::OpenProcess;
use winapi::um::winnt::{PROCESS_SET_QUOTA, PROCESS_TERMINATE};
let handle = unsafe { OpenProcess(PROCESS_SET_QUOTA | PROCESS_TERMINATE, 0, pid) };
if handle.is_null() { return Err(std::io::Error::last_os_error()); }
let result = unsafe {
winapi::um::jobapi2::AssignProcessToJobObject(
self.0 as winapi::shared::ntdef::HANDLE,
handle,
)
};
unsafe { CloseHandle(handle) };
if result == 0 { return Err(std::io::Error::last_os_error()); }
Ok(())
}
}
#[cfg(feature = "pty")]
impl Drop for PtyProcessGuard {
fn drop(&mut self) {
unsafe { winapi::um::handleapi::CloseHandle(self.0 as winapi::shared::ntdef::HANDLE) };
}
}
#[cfg(feature = "pty")]
#[derive(Debug, Clone)]
pub struct ChildProcessInfo { pub pid: u32, pub name: String }
#[cfg(feature = "pty")]
#[derive(Debug, Clone)]
pub struct OrphanConhostInfo { pub pid: u32, pub parent_pid: u32, pub parent_name: String }
#[cfg(feature = "pty")]
pub fn find_child_processes(parent_pid: u32) -> Vec<ChildProcessInfo> {
use winapi::um::handleapi::CloseHandle;
use winapi::um::tlhelp32::{CreateToolhelp32Snapshot, Process32First, Process32Next, PROCESSENTRY32, TH32CS_SNAPPROCESS};
let mut children = Vec::new();
let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) };
if snapshot == winapi::um::handleapi::INVALID_HANDLE_VALUE { return children; }
let mut entry: PROCESSENTRY32 = unsafe { std::mem::zeroed() };
entry.dwSize = std::mem::size_of::<PROCESSENTRY32>() as u32;
if unsafe { Process32First(snapshot, &mut entry) } != 0 {
loop {
if entry.th32ParentProcessID == parent_pid {
let length = entry.szExeFile.iter().position(|&byte| byte == 0).unwrap_or(entry.szExeFile.len());
let name = String::from_utf8_lossy(&entry.szExeFile[..length].iter().map(|&byte| byte as u8).collect::<Vec<_>>()).into_owned();
children.push(ChildProcessInfo { pid: entry.th32ProcessID, name });
}
if unsafe { Process32Next(snapshot, &mut entry) } == 0 { break; }
}
}
unsafe { CloseHandle(snapshot) };
children
}
#[cfg(feature = "pty")]
fn conhost_children_of_current_process() -> Vec<u32> {
find_child_processes(std::process::id()).into_iter()
.filter(|child| child.name.eq_ignore_ascii_case("conhost.exe"))
.map(|child| child.pid).collect()
}
#[cfg(feature = "pty")]
pub fn find_orphan_conhosts() -> Vec<OrphanConhostInfo> {
use winapi::um::handleapi::CloseHandle;
use winapi::um::tlhelp32::{CreateToolhelp32Snapshot, Process32First, Process32Next, PROCESSENTRY32, TH32CS_SNAPPROCESS};
let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) };
if snapshot == winapi::um::handleapi::INVALID_HANDLE_VALUE { return Vec::new(); }
let mut entry: PROCESSENTRY32 = unsafe { std::mem::zeroed() };
entry.dwSize = std::mem::size_of::<PROCESSENTRY32>() as u32;
let mut all_pids = std::collections::HashSet::new();
let mut conhosts = Vec::new();
let mut names = std::collections::HashMap::new();
if unsafe { Process32First(snapshot, &mut entry) } != 0 {
loop {
let length = entry.szExeFile.iter().position(|&byte| byte == 0).unwrap_or(entry.szExeFile.len());
let name = String::from_utf8_lossy(&entry.szExeFile[..length].iter().map(|&byte| byte as u8).collect::<Vec<_>>()).into_owned();
all_pids.insert(entry.th32ProcessID);
names.insert(entry.th32ProcessID, name.clone());
if name.eq_ignore_ascii_case("conhost.exe") { conhosts.push((entry.th32ProcessID, entry.th32ParentProcessID)); }
if unsafe { Process32Next(snapshot, &mut entry) } == 0 { break; }
}
}
unsafe { CloseHandle(snapshot) };
conhosts.into_iter().filter(|(_, parent)| !all_pids.contains(parent)).map(|(pid, parent_pid)| OrphanConhostInfo {
pid, parent_pid, parent_name: names.get(&parent_pid).cloned().unwrap_or_default(),
}).collect()
}
#[cfg(feature = "pty")]
pub fn before_pty_spawn() -> PtySpawnContext {
PtySpawnContext(conhost_children_of_current_process())
}
#[cfg(feature = "pty")]
fn apply_priority(handle: *mut std::ffi::c_void, nice: Option<i32>) -> std::io::Result<()> {
use winapi::um::processthreadsapi::SetPriorityClass;
use winapi::um::winbase::{ABOVE_NORMAL_PRIORITY_CLASS, BELOW_NORMAL_PRIORITY_CLASS, HIGH_PRIORITY_CLASS, IDLE_PRIORITY_CLASS};
let flags = match nice {
Some(value) if value >= 15 => IDLE_PRIORITY_CLASS,
Some(value) if value >= 1 => BELOW_NORMAL_PRIORITY_CLASS,
Some(value) if value <= -15 => HIGH_PRIORITY_CLASS,
Some(value) if value <= -1 => ABOVE_NORMAL_PRIORITY_CLASS,
_ => 0,
};
if flags != 0 && unsafe { SetPriorityClass(handle.cast(), flags) } == 0 {
return Err(std::io::Error::last_os_error());
}
Ok(())
}
#[cfg(feature = "pty")]
fn prepare_conpty_child(
context: PtySpawnContext,
handle: *mut std::ffi::c_void,
nice: Option<i32>,
) -> std::io::Result<PtyProcessGuard> {
use winapi::shared::minwindef::FALSE;
use winapi::um::jobapi2::{AssignProcessToJobObject, CreateJobObjectW, SetInformationJobObject};
use winapi::um::winnt::{JobObjectExtendedLimitInformation, JOBOBJECT_EXTENDED_LIMIT_INFORMATION, JOB_OBJECT_LIMIT_BREAKAWAY_OK, JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE};
let job = unsafe { CreateJobObjectW(std::ptr::null_mut(), std::ptr::null()) };
if job.is_null() || job == winapi::um::handleapi::INVALID_HANDLE_VALUE { return Err(std::io::Error::last_os_error()); }
let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = unsafe { std::mem::zeroed() };
info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_BREAKAWAY_OK;
let configured = unsafe { SetInformationJobObject(job, JobObjectExtendedLimitInformation, (&mut info as *mut JOBOBJECT_EXTENDED_LIMIT_INFORMATION).cast(), std::mem::size_of::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>() as u32) };
if configured == FALSE {
let error = std::io::Error::last_os_error(); unsafe { winapi::um::handleapi::CloseHandle(job) }; return Err(error);
}
if unsafe { AssignProcessToJobObject(job, handle.cast()) } == FALSE {
let error = std::io::Error::last_os_error(); unsafe { winapi::um::handleapi::CloseHandle(job) }; return Err(error);
}
let guard = PtyProcessGuard(job as usize);
for pid in conhost_children_of_current_process() {
if !context.0.contains(&pid) { let _ = guard.assign_pid(pid); }
}
apply_priority(handle, nice)?;
Ok(guard)
}
#[cfg(feature = "pty")]
pub fn prepare_unmanaged_pty_child(
_context: PtySpawnContext,
_nice: Option<i32>,
) -> std::io::Result<PtyProcessGuard> {
Err(std::io::Error::other(
"Pseudo-terminal child does not expose a Windows process handle",
))
}
#[cfg(feature = "pty")]
pub fn input_payload(data: &[u8]) -> Vec<u8> {
let mut translated = Vec::with_capacity(data.len());
let mut index = 0;
while index < data.len() {
match data[index] {
b'\r' => {
translated.push(b'\r');
if data.get(index + 1) == Some(&b'\n') { translated.push(b'\n'); index += 2; } else { index += 1; }
}
b'\n' => { translated.push(b'\r'); index += 1; }
byte => { translated.push(byte); index += 1; }
}
}
translated
}
#[cfg(feature = "pty")]
pub fn query_responses(data: &[u8]) -> Vec<Vec<u8>> {
let query = b"\x1b[6n";
data.windows(query.len()).filter(|window| *window == query).map(|_| b"\x1b[1;1R".to_vec()).collect()
}
#[cfg(feature = "pty")]
pub fn shell_argv(command: &str) -> Vec<String> {
vec!["cmd.exe".into(), "/C".into(), command.into()]
}
#[cfg(feature = "pty")]
pub fn wait_before_pty_close_supported() -> bool { false }
#[cfg(feature = "pty")]
pub fn is_ignorable_process_control_error(error: &std::io::Error) -> bool {
matches!(error.kind(), std::io::ErrorKind::NotFound | std::io::ErrorKind::InvalidInput)
}
#[cfg(feature = "pty")]
pub fn terminate_pty_child(_pid: u32) -> std::io::Result<bool> { Ok(true) }
#[cfg(feature = "pty")]
pub fn signal_pty_tree(_pid: u32, _force: bool) -> std::io::Result<bool> { Ok(true) }
#[cfg(feature = "pty")]
pub fn resize_pty(
_master: &dyn crate::platform::terminal::PtyMaster,
_size: crate::platform::terminal::PtySize,
) -> std::io::Result<()> { Ok(()) }
#[cfg(feature = "pty")]
pub struct TerminalInputSession(super::terminal_input::TerminalInputCore);
#[cfg(feature = "pty")]
impl TerminalInputSession {
pub fn new() -> std::io::Result<Option<Self>> {
let input = super::terminal_input::TerminalInputCore::new();
input.start_impl()?;
Ok(Some(Self(input)))
}
pub fn read_chunk(&self, timeout: std::time::Duration) -> std::io::Result<Option<PtyInputChunk>> {
use super::terminal_input::{TerminalInputWaitOutcome, wait_for_terminal_input_event};
match wait_for_terminal_input_event(&self.0.state, &self.0.condvar, Some(timeout)) {
TerminalInputWaitOutcome::Event(event) => Ok(Some(PtyInputChunk { data: event.data, submit: event.submit })),
TerminalInputWaitOutcome::Timeout => Ok(None),
TerminalInputWaitOutcome::Closed => Err(std::io::Error::new(std::io::ErrorKind::BrokenPipe, "native terminal input closed")),
}
}
}
#[cfg(feature = "pty")]
impl Drop for TerminalInputSession {
fn drop(&mut self) { let _ = self.0.stop_impl(); }
}
#[cfg(all(test, feature = "pty"))]
mod tests {
use super::*;
#[test]
fn assign_child_to_job_null_handle_errors() {
assert!(prepare_unmanaged_pty_child(before_pty_spawn(), None).is_err());
}
#[test]
fn apply_windows_pty_priority_zero_nice_noop() {
let handle = std::ptr::NonNull::<std::ffi::c_void>::dangling().as_ptr();
assert!(apply_priority(handle, Some(0)).is_ok());
assert!(apply_priority(handle, None).is_ok());
}
}