diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-03-31 13:29:57 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-03-31 13:29:57 +0300 |
| commit | f8c452294d6f76ccb264c0d9e205398492f00e18 (patch) | |
| tree | 68f9505424de441b5da4dbe26fab6149e2a75734 /src/platform | |
| parent | b8ccdf600c750411b439d0eb41f6a70e3c595f4f (diff) | |
fix: terminate pane sessions on close
Diffstat (limited to 'src/platform')
| -rw-r--r-- | src/platform/fallback.rs | 15 | ||||
| -rw-r--r-- | src/platform/linux.rs | 63 | ||||
| -rw-r--r-- | src/platform/macos.rs | 61 | ||||
| -rw-r--r-- | src/platform/mod.rs | 7 |
4 files changed, 143 insertions, 3 deletions
diff --git a/src/platform/fallback.rs b/src/platform/fallback.rs index 7bcd23c5..4bf78352 100644 --- a/src/platform/fallback.rs +++ b/src/platform/fallback.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use super::ForegroundJob; +use super::{ForegroundJob, Signal}; /// Unsupported platform stub. pub fn foreground_job(_child_pid: u32) -> Option<ForegroundJob> { @@ -11,3 +11,16 @@ pub fn foreground_job(_child_pid: u32) -> Option<ForegroundJob> { pub fn process_cwd(_pid: u32) -> Option<PathBuf> { None } + +/// Unsupported platform stub. +pub fn session_processes(_child_pid: u32) -> Vec<u32> { + Vec::new() +} + +/// Unsupported platform stub. +pub fn signal_processes(_pids: &[u32], _signal: Signal) {} + +/// Unsupported platform stub. +pub fn process_exists(_pid: u32) -> bool { + false +} diff --git a/src/platform/linux.rs b/src/platform/linux.rs index 10695e92..b6fc0272 100644 --- a/src/platform/linux.rs +++ b/src/platform/linux.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use super::{ForegroundJob, ForegroundProcess}; +use super::{ForegroundJob, ForegroundProcess, Signal}; /// Collect the foreground terminal job for a given child PID. pub fn foreground_job(child_pid: u32) -> Option<ForegroundJob> { @@ -87,3 +87,64 @@ pub fn process_cwd(pid: u32) -> Option<PathBuf> { } std::fs::read_link(format!("/proc/{pid}/cwd")).ok() } + +pub fn session_processes(child_pid: u32) -> Vec<u32> { + let Some(session_id) = process_session_id(child_pid) else { + return Vec::new(); + }; + + let mut pids = Vec::new(); + for entry in std::fs::read_dir("/proc").into_iter().flatten().flatten() { + let file_name = entry.file_name(); + let Some(pid_str) = file_name.to_str() else { + continue; + }; + if !pid_str.bytes().all(|b| b.is_ascii_digit()) { + continue; + } + + let Ok(pid) = pid_str.parse::<u32>() else { + continue; + }; + if process_session_id(pid) == Some(session_id) { + pids.push(pid); + } + } + pids +} + +pub fn signal_processes(pids: &[u32], signal: Signal) { + let sig = match signal { + Signal::Hangup => libc::SIGHUP, + Signal::Terminate => libc::SIGTERM, + Signal::Kill => libc::SIGKILL, + }; + + for &pid in pids { + if pid == 0 { + continue; + } + unsafe { + libc::kill(pid as i32, sig); + } + } +} + +pub fn process_exists(pid: u32) -> bool { + if pid == 0 { + return false; + } + let result = unsafe { libc::kill(pid as i32, 0) }; + if result == 0 { + true + } else { + std::io::Error::last_os_error().raw_os_error() == Some(libc::EPERM) + } +} + +fn process_session_id(pid: u32) -> Option<i32> { + let stat = std::fs::read_to_string(format!("/proc/{pid}/stat")).ok()?; + let rest = stat.get(stat.rfind(')')? + 2..)?; + let fields: Vec<&str> = rest.split_whitespace().collect(); + fields.get(3)?.parse().ok() +} diff --git a/src/platform/macos.rs b/src/platform/macos.rs index e2f368f8..82de0c5b 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -2,7 +2,7 @@ use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt; use std::path::{Path, PathBuf}; -use super::{ForegroundJob, ForegroundProcess}; +use super::{ForegroundJob, ForegroundProcess, Signal}; /// Collect the foreground terminal job for a given child PID. pub fn foreground_job(child_pid: u32) -> Option<ForegroundJob> { @@ -284,3 +284,62 @@ pub fn process_cwd(pid: u32) -> Option<PathBuf> { } Some(PathBuf::from(OsStr::from_bytes(&vip_path[..nul]))) } + +pub fn session_processes(child_pid: u32) -> Vec<u32> { + if child_pid == 0 { + return Vec::new(); + } + + let target_session = unsafe { libc::getsid(child_pid as libc::c_int) }; + if target_session <= 0 { + return Vec::new(); + } + + let mut pids = vec![0i32; 4096]; + let bytes = unsafe { + libc::proc_listallpids( + pids.as_mut_ptr() as *mut libc::c_void, + (pids.len() * std::mem::size_of::<i32>()) as libc::c_int, + ) + }; + if bytes <= 0 { + return Vec::new(); + } + + let count = (bytes as usize) / std::mem::size_of::<i32>(); + pids.into_iter() + .take(count) + .filter(|pid| *pid > 0) + .filter(|pid| unsafe { libc::getsid(*pid) } == target_session) + .map(|pid| pid as u32) + .collect() +} + +pub fn signal_processes(pids: &[u32], signal: Signal) { + let sig = match signal { + Signal::Hangup => libc::SIGHUP, + Signal::Terminate => libc::SIGTERM, + Signal::Kill => libc::SIGKILL, + }; + + for &pid in pids { + if pid == 0 { + continue; + } + unsafe { + libc::kill(pid as libc::c_int, sig); + } + } +} + +pub fn process_exists(pid: u32) -> bool { + if pid == 0 { + return false; + } + let result = unsafe { libc::kill(pid as libc::c_int, 0) }; + if result == 0 { + true + } else { + std::io::Error::last_os_error().raw_os_error() == Some(libc::EPERM) + } +} diff --git a/src/platform/mod.rs b/src/platform/mod.rs index ade86249..8023cd11 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -17,6 +17,13 @@ pub struct ForegroundJob { pub processes: Vec<ForegroundProcess>, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Signal { + Hangup, + Terminate, + Kill, +} + #[cfg(target_os = "linux")] mod linux; #[cfg(target_os = "linux")] |
