diff --git a/crates/running-process-platform-internal/src/platform/terminal.rs b/crates/running-process-platform-internal/src/platform/terminal.rs index 1d770c96..071daafe 100644 --- a/crates/running-process-platform-internal/src/platform/terminal.rs +++ b/crates/running-process-platform-internal/src/platform/terminal.rs @@ -1,5 +1,25 @@ //! Terminal, PTY, console, input, and terminal-I/O primitives. +// Shared image classification is pure, but only Windows currently consumes it. +#[cfg(feature = "pty")] +#[allow(dead_code)] +pub(crate) fn is_conpty_host_image(name: &str) -> bool { + name.eq_ignore_ascii_case("conhost.exe") || name.eq_ignore_ascii_case("OpenConsole.exe") +} + +#[cfg(all(test, feature = "pty"))] +mod console_host_tests { + use super::is_conpty_host_image; + + #[test] + fn recognizes_system_and_sidecar_console_hosts() { + assert!(is_conpty_host_image("conhost.exe")); + assert!(is_conpty_host_image("OPENCONSOLE.EXE")); + assert!(!is_conpty_host_image("OpenConsole.exe.bak")); + assert!(!is_conpty_host_image("console.exe")); + } +} + use std::ffi::OsString; use std::io::{self, Read, Write}; use std::path::Path; diff --git a/crates/running-process-platform-internal/src/platform_linux/terminal.rs b/crates/running-process-platform-internal/src/platform_linux/terminal.rs index 0aa37174..713b08a2 100644 --- a/crates/running-process-platform-internal/src/platform_linux/terminal.rs +++ b/crates/running-process-platform-internal/src/platform_linux/terminal.rs @@ -225,6 +225,7 @@ pub struct OrphanConhostInfo { pub pid: u32, pub parent_pid: u32, pub parent_name: String, + pub host_name: String, } #[cfg(feature = "pty")] diff --git a/crates/running-process-platform-internal/src/platform_macos/terminal.rs b/crates/running-process-platform-internal/src/platform_macos/terminal.rs index dedb5925..466ed5b8 100644 --- a/crates/running-process-platform-internal/src/platform_macos/terminal.rs +++ b/crates/running-process-platform-internal/src/platform_macos/terminal.rs @@ -225,6 +225,7 @@ pub struct OrphanConhostInfo { pub pid: u32, pub parent_pid: u32, pub parent_name: String, + pub host_name: String, } #[cfg(feature = "pty")] diff --git a/crates/running-process-platform-internal/src/platform_win/terminal.rs b/crates/running-process-platform-internal/src/platform_win/terminal.rs index 746d9bdc..d1287822 100644 --- a/crates/running-process-platform-internal/src/platform_win/terminal.rs +++ b/crates/running-process-platform-internal/src/platform_win/terminal.rs @@ -165,7 +165,7 @@ 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 } +pub struct OrphanConhostInfo { pub pid: u32, pub parent_pid: u32, pub parent_name: String, pub host_name: String } #[cfg(feature = "pty")] pub fn find_child_processes(parent_pid: u32) -> Vec { @@ -192,13 +192,16 @@ pub fn find_child_processes(parent_pid: u32) -> Vec { #[cfg(feature = "pty")] fn conhost_children_of_current_process() -> Vec { + use crate::platform::terminal::is_conpty_host_image; find_child_processes(std::process::id()).into_iter() - .filter(|child| child.name.eq_ignore_ascii_case("conhost.exe")) + .filter(|child| is_conpty_host_image(&child.name)) .map(|child| child.pid).collect() } +#[cfg(feature = "pty")] #[cfg(feature = "pty")] pub fn find_orphan_conhosts() -> Vec { + use crate::platform::terminal::is_conpty_host_image; use winapi::um::handleapi::CloseHandle; use winapi::um::tlhelp32::{CreateToolhelp32Snapshot, Process32First, Process32Next, PROCESSENTRY32, TH32CS_SNAPPROCESS}; let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) }; @@ -214,13 +217,13 @@ pub fn find_orphan_conhosts() -> Vec { let name = String::from_utf8_lossy(&entry.szExeFile[..length].iter().map(|&byte| byte as u8).collect::>()).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 is_conpty_host_image(&name) { conhosts.push((entry.th32ProcessID, entry.th32ParentProcessID, name)); } 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(), + conhosts.into_iter().filter(|(_, parent, _)| !all_pids.contains(parent)).map(|(pid, parent_pid, host_name)| OrphanConhostInfo { + pid, parent_pid, parent_name: names.get(&parent_pid).cloned().unwrap_or_default(), host_name, }).collect() } diff --git a/crates/running-process/src/daemon/reaper.rs b/crates/running-process/src/daemon/reaper.rs index 033caf53..b90f63dc 100644 --- a/crates/running-process/src/daemon/reaper.rs +++ b/crates/running-process/src/daemon/reaper.rs @@ -117,9 +117,9 @@ pub fn scan_for_zombies(state: &DaemonState) -> Vec { zombies } -/// Scan for orphaned conhost.exe processes system-wide. +/// Scan for orphaned ConPTY console-host processes system-wide. /// -/// These are conhost.exe instances whose parent process has died — typically +/// These are conhost.exe or OpenConsole.exe instances whose parent has died — typically /// leftovers from ConPTY sessions that were not properly cleaned up. /// Unlike registry-based zombie scanning, this uses a Toolhelp process snapshot /// and does not require prior registration. @@ -129,8 +129,11 @@ pub fn scan_for_orphan_conhosts() -> Vec { .into_iter() .map(|c| ZombieInfo { pid: c.pid, - command: "conhost.exe".to_string(), - reason: format!("orphan conhost.exe — parent PID {} is dead", c.parent_pid), + command: c.host_name.clone(), + reason: format!( + "orphan {} — parent PID {} is dead", + c.host_name, c.parent_pid + ), }) .collect() } diff --git a/crates/running-process/src/pty/mod.rs b/crates/running-process/src/pty/mod.rs index 35f49fc3..67aafaea 100644 --- a/crates/running-process/src/pty/mod.rs +++ b/crates/running-process/src/pty/mod.rs @@ -558,11 +558,11 @@ pub fn find_child_processes(parent_pid: u32) -> Vec { pty_platform::find_child_processes(parent_pid) } -/// A conhost.exe process whose parent is no longer alive — likely an orphan +/// A ConPTY console host whose parent is no longer alive — likely an orphan /// from a dead ConPTY session. pub use pty_platform::OrphanConhostInfo; -/// Scan all conhost.exe processes on the system and return those whose parent +/// Scan conhost.exe and OpenConsole.exe processes and return those whose parent /// process is no longer alive. These are likely orphans from dead ConPTY sessions. /// /// Uses `CreateToolhelp32Snapshot` for a point-in-time snapshot — no sysinfo