use std::io;
use std::os::fd::RawFd;
use std::process::Child;
use nix::libc;
use tokio::time::{Duration, sleep};
use crate::domain::errors::{AgentError, AgentResult, ErrorCode};
use crate::infrastructure::pty_session::signal_failure_should_be_ignored;
pub(crate) fn configure_child_terminal(slave_fd: RawFd) -> io::Result<()> {
match unsafe { libc::setsid() } {
-1 => {
let err = io::Error::last_os_error();
if err.raw_os_error() == Some(libc::EPERM) {
if unsafe { libc::setpgid(0, 0) } == -1 {
return Err(io::Error::last_os_error());
}
acquire_controlling_terminal(slave_fd)
} else {
Err(err)
}
}
_ => acquire_controlling_terminal(slave_fd),
}
}
pub(crate) async fn terminate_child(child: &mut Child, pgid: i32) -> AgentResult<()> {
let _ = child.try_wait().map_err(|e| kill_err(e.to_string()))?;
if let Err(err) = send_signal(-pgid, libc::SIGTERM) {
handle_signal_error(child, err)?;
}
sleep(Duration::from_millis(100)).await;
if let Err(err) = send_signal(-pgid, libc::SIGKILL) {
handle_signal_error(child, err)?;
}
wait_for_exit(child).await
}
fn acquire_controlling_terminal(slave_fd: RawFd) -> io::Result<()> {
#[cfg(target_os = "linux")]
let request: libc::c_ulong = libc::TIOCSCTTY;
#[cfg(not(target_os = "linux"))]
let request: libc::c_ulong = libc::TIOCSCTTY.into();
if unsafe { libc::ioctl(slave_fd, request, 0) } == -1 {
return Err(io::Error::last_os_error());
}
Ok(())
}
fn handle_signal_error(child: &mut Child, err: io::Error) -> AgentResult<()> {
let leader_exited = child
.try_wait()
.map_err(|e| kill_err(e.to_string()))?
.is_some();
if signal_failure_should_be_ignored(err.raw_os_error(), leader_exited) {
Ok(())
} else {
Err(kill_err(err.to_string()))
}
}
async fn wait_for_exit(child: &mut Child) -> AgentResult<()> {
loop {
match child.try_wait().map_err(|e| kill_err(e.to_string()))? {
Some(_) => return Ok(()),
None => sleep(Duration::from_millis(20)).await,
}
}
}
fn send_signal(pid: i32, signal: i32) -> io::Result<()> {
match unsafe { libc::kill(pid, signal) } {
0 => Ok(()),
_ => Err(io::Error::last_os_error()),
}
}
fn kill_err(message: impl Into<String>) -> AgentError {
AgentError::new(ErrorCode::PtyKillFailed, message)
}