aboutsummaryrefslogtreecommitdiff
path: root/src/platform
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-03-31 13:29:57 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-03-31 13:29:57 +0300
commitf8c452294d6f76ccb264c0d9e205398492f00e18 (patch)
tree68f9505424de441b5da4dbe26fab6149e2a75734 /src/platform
parentb8ccdf600c750411b439d0eb41f6a70e3c595f4f (diff)
fix: terminate pane sessions on close
Diffstat (limited to 'src/platform')
-rw-r--r--src/platform/fallback.rs15
-rw-r--r--src/platform/linux.rs63
-rw-r--r--src/platform/macos.rs61
-rw-r--r--src/platform/mod.rs7
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")]