use std::io;
use crate::Termination;
pub fn exit_without_handlers(code: u8) -> ! {
#[cfg(unix)]
{
unsafe { libc::_exit(libc::c_int::from(code)) }
}
#[cfg(windows)]
{
use windows_sys::Win32::System::Threading::{GetCurrentProcess, TerminateProcess};
if unsafe { TerminateProcess(GetCurrentProcess(), u32::from(code)) } != 0 {
loop {
std::thread::park();
}
}
std::process::exit(i32::from(code))
}
#[cfg(not(any(unix, windows)))]
std::process::exit(i32::from(code))
}
pub fn stop_std_handle_inheritance() {
#[cfg(windows)]
{
use windows_sys::Win32::Foundation::{
HANDLE_FLAG_INHERIT, INVALID_HANDLE_VALUE, SetHandleInformation,
};
use windows_sys::Win32::System::Console::{
GetStdHandle, STD_ERROR_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE,
};
for stream in [STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE] {
let handle = unsafe { GetStdHandle(stream) };
if handle.is_null() || handle == INVALID_HANDLE_VALUE {
continue;
}
let _ = unsafe { SetHandleInformation(handle, HANDLE_FLAG_INHERIT, 0) };
}
}
}
const PARENT_EXITED: Termination = Termination::new("PARENT_EXITED", 143, false);
pub fn watch_parent() -> io::Result<()> {
let handler = crate::platform::termination_handler().ok_or_else(|| {
io::Error::new(
io::ErrorKind::NotFound,
"no termination handler is installed",
)
})?;
let Some(parent) = platform::Parent::open()? else {
return Ok(());
};
std::thread::Builder::new()
.name("alpha2-parent-watch".to_owned())
.spawn(move || {
if parent.wait() {
handler(PARENT_EXITED);
}
})?;
Ok(())
}
pub struct KillOnCloseJob {
#[cfg(windows)]
handle: platform::OwnedHandle,
}
impl KillOnCloseJob {
pub fn new() -> io::Result<Self> {
Ok(Self {
#[cfg(windows)]
handle: platform::kill_on_close_job()?,
})
}
pub fn assign(&self, child: &std::process::Child) -> io::Result<()> {
#[cfg(windows)]
{
platform::assign(&self.handle, child)
}
#[cfg(not(windows))]
{
let _ = child;
Ok(())
}
}
}
impl std::fmt::Debug for KillOnCloseJob {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("KillOnCloseJob")
.finish_non_exhaustive()
}
}
#[cfg(target_os = "linux")]
mod platform {
use std::io;
pub(super) struct Parent(Option<std::fs::File>);
impl Parent {
pub(super) fn open() -> io::Result<Option<Self>> {
use std::os::fd::FromRawFd;
let parent = unsafe { libc::getppid() };
if parent <= 1 {
return Ok(None);
}
let fd = unsafe { libc::syscall(libc::SYS_pidfd_open, parent, 0) };
if fd < 0 {
let error = io::Error::last_os_error();
return match error.raw_os_error() {
Some(libc::ESRCH) => Ok(Some(Self(None))),
Some(libc::ENOSYS) => death_signal(parent),
_ => Err(error),
};
}
let fd = libc::c_int::try_from(fd)
.map_err(|_| io::Error::other("pidfd_open returned an impossible descriptor"))?;
let pidfd = unsafe { std::fs::File::from_raw_fd(fd) };
if unsafe { libc::getppid() } != parent {
return Ok(Some(Self(None)));
}
Ok(Some(Self(Some(pidfd))))
}
pub(super) fn wait(&self) -> bool {
use std::os::fd::AsRawFd;
let Some(pidfd) = &self.0 else {
return true;
};
let mut poll = libc::pollfd {
fd: pidfd.as_raw_fd(),
events: libc::POLLIN,
revents: 0,
};
loop {
let ready = unsafe { libc::poll(&raw mut poll, 1, -1) };
if ready > 0 {
return true;
}
if ready < 0 && io::Error::last_os_error().kind() != io::ErrorKind::Interrupted {
return false;
}
}
}
}
fn death_signal(parent: libc::pid_t) -> io::Result<Option<Parent>> {
if unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGTERM) } != 0 {
return Err(io::Error::last_os_error());
}
if unsafe { libc::getppid() } != parent {
return Ok(Some(Parent(None)));
}
Ok(None)
}
}
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))]
mod platform {
use std::io;
pub(super) struct Parent(Option<std::fs::File>);
impl Parent {
pub(super) fn open() -> io::Result<Option<Self>> {
use std::os::fd::{AsRawFd, FromRawFd};
let parent = unsafe { libc::getppid() };
if parent <= 1 {
return Ok(None);
}
let fd = unsafe { libc::kqueue() };
if fd < 0 {
return Err(io::Error::last_os_error());
}
let queue = unsafe { std::fs::File::from_raw_fd(fd) };
let mut change: libc::kevent = unsafe { std::mem::zeroed() };
change.ident = usize::try_from(parent).unwrap_or(0);
change.filter = libc::EVFILT_PROC;
change.flags = libc::EV_ADD | libc::EV_ONESHOT;
change.fflags = libc::NOTE_EXIT;
let registered = unsafe {
libc::kevent(
queue.as_raw_fd(),
&raw const change,
1,
std::ptr::null_mut(),
0,
std::ptr::null(),
)
};
if registered < 0 {
let error = io::Error::last_os_error();
return if error.raw_os_error() == Some(libc::ESRCH) {
Ok(Some(Self(None)))
} else {
Err(error)
};
}
if unsafe { libc::getppid() } != parent {
return Ok(Some(Self(None)));
}
Ok(Some(Self(Some(queue))))
}
pub(super) fn wait(&self) -> bool {
use std::os::fd::AsRawFd;
let Some(queue) = &self.0 else {
return true;
};
loop {
let mut event: libc::kevent = unsafe { std::mem::zeroed() };
let ready = unsafe {
libc::kevent(
queue.as_raw_fd(),
std::ptr::null(),
0,
&raw mut event,
1,
std::ptr::null(),
)
};
if ready > 0 {
return true;
}
if ready < 0 && io::Error::last_os_error().kind() != io::ErrorKind::Interrupted {
return false;
}
}
}
}
}
#[cfg(windows)]
mod platform {
use std::io;
use std::os::windows::io::AsRawHandle;
use windows_sys::Win32::Foundation::{
CloseHandle, ERROR_INVALID_PARAMETER, FILETIME, HANDLE, INVALID_HANDLE_VALUE, WAIT_OBJECT_0,
};
use windows_sys::Win32::System::Diagnostics::ToolHelp::{
CreateToolhelp32Snapshot, PROCESSENTRY32W, Process32FirstW, Process32NextW,
TH32CS_SNAPPROCESS,
};
use windows_sys::Win32::System::JobObjects::{
AssignProcessToJobObject, CreateJobObjectW, JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,
JOBOBJECT_EXTENDED_LIMIT_INFORMATION, JobObjectExtendedLimitInformation,
SetInformationJobObject,
};
use windows_sys::Win32::System::Threading::{
GetCurrentProcess, GetCurrentProcessId, GetProcessTimes, INFINITE, OpenProcess,
PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_SYNCHRONIZE, WaitForSingleObject,
};
pub(super) struct OwnedHandle(HANDLE);
unsafe impl Send for OwnedHandle {}
unsafe impl Sync for OwnedHandle {}
impl Drop for OwnedHandle {
fn drop(&mut self) {
unsafe { CloseHandle(self.0) };
}
}
pub(super) struct Parent(Option<OwnedHandle>);
impl Parent {
pub(super) fn open() -> io::Result<Option<Self>> {
let Some(parent) = parent_id()? else {
return Ok(None);
};
let handle = unsafe {
OpenProcess(
PROCESS_SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION,
0,
parent,
)
};
if handle.is_null() {
let error = io::Error::last_os_error();
return if error.raw_os_error() == i32::try_from(ERROR_INVALID_PARAMETER).ok() {
Ok(Some(Self(None)))
} else {
Err(error)
};
}
let handle = OwnedHandle(handle);
let ours = created(unsafe { GetCurrentProcess() })?;
if created(handle.0)? > ours {
return Ok(Some(Self(None)));
}
Ok(Some(Self(Some(handle))))
}
pub(super) fn wait(&self) -> bool {
let Some(handle) = &self.0 else {
return true;
};
unsafe { WaitForSingleObject(handle.0, INFINITE) == WAIT_OBJECT_0 }
}
}
fn parent_id() -> io::Result<Option<u32>> {
let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) };
if snapshot == INVALID_HANDLE_VALUE {
return Err(io::Error::last_os_error());
}
let snapshot = OwnedHandle(snapshot);
let ours = unsafe { GetCurrentProcessId() };
let mut entry = PROCESSENTRY32W {
dwSize: u32::try_from(std::mem::size_of::<PROCESSENTRY32W>()).unwrap_or(0),
..PROCESSENTRY32W::default()
};
let mut more = unsafe { Process32FirstW(snapshot.0, &raw mut entry) } != 0;
while more {
if entry.th32ProcessID == ours {
return Ok(Some(entry.th32ParentProcessID).filter(|parent| *parent != 0));
}
more = unsafe { Process32NextW(snapshot.0, &raw mut entry) } != 0;
}
Ok(None)
}
fn created(process: HANDLE) -> io::Result<u64> {
let zero = || FILETIME {
dwLowDateTime: 0,
dwHighDateTime: 0,
};
let (mut created, mut exited, mut kernel, mut user) = (zero(), zero(), zero(), zero());
let read = unsafe {
GetProcessTimes(
process,
&raw mut created,
&raw mut exited,
&raw mut kernel,
&raw mut user,
)
};
if read == 0 {
return Err(io::Error::last_os_error());
}
Ok(u64::from(created.dwHighDateTime) << 32 | u64::from(created.dwLowDateTime))
}
pub(super) fn kill_on_close_job() -> io::Result<OwnedHandle> {
let job = unsafe { CreateJobObjectW(std::ptr::null(), std::ptr::null()) };
if job.is_null() {
return Err(io::Error::last_os_error());
}
let job = OwnedHandle(job);
let mut limits = JOBOBJECT_EXTENDED_LIMIT_INFORMATION::default();
limits.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
let set = unsafe {
SetInformationJobObject(
job.0,
JobObjectExtendedLimitInformation,
(&raw const limits).cast(),
u32::try_from(std::mem::size_of::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>())
.unwrap_or(0),
)
};
if set == 0 {
return Err(io::Error::last_os_error());
}
Ok(job)
}
pub(super) fn assign(job: &OwnedHandle, child: &std::process::Child) -> io::Result<()> {
if unsafe { AssignProcessToJobObject(job.0, child.as_raw_handle()) } == 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
}
#[cfg(not(any(
target_os = "linux",
target_os = "macos",
target_os = "ios",
target_os = "freebsd",
windows
)))]
mod platform {
use std::io;
pub(super) struct Parent;
impl Parent {
pub(super) fn open() -> io::Result<Option<Self>> {
Ok(None)
}
pub(super) fn wait(&self) -> bool {
false
}
}
}
#[cfg(test)]
#[path = "lifetime_tests.rs"]
mod tests;