1use std::io;
5
6use crate::Termination;
7
8pub fn exit_without_handlers(code: u8) -> ! {
14 #[cfg(unix)]
15 {
16 unsafe { libc::_exit(libc::c_int::from(code)) }
18 }
19 #[cfg(windows)]
20 {
21 use windows_sys::Win32::System::Threading::{GetCurrentProcess, TerminateProcess};
22 if unsafe { TerminateProcess(GetCurrentProcess(), u32::from(code)) } != 0 {
24 loop {
25 std::thread::park();
26 }
27 }
28 std::process::exit(i32::from(code))
29 }
30 #[cfg(not(any(unix, windows)))]
31 std::process::exit(i32::from(code))
32}
33
34pub fn stop_std_handle_inheritance() {
40 #[cfg(windows)]
41 {
42 use windows_sys::Win32::Foundation::{
43 HANDLE_FLAG_INHERIT, INVALID_HANDLE_VALUE, SetHandleInformation,
44 };
45 use windows_sys::Win32::System::Console::{
46 GetStdHandle, STD_ERROR_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE,
47 };
48 for stream in [STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE] {
49 let handle = unsafe { GetStdHandle(stream) };
51 if handle.is_null() || handle == INVALID_HANDLE_VALUE {
52 continue;
53 }
54 let _ = unsafe { SetHandleInformation(handle, HANDLE_FLAG_INHERIT, 0) };
56 }
57 }
58}
59
60const PARENT_EXITED: Termination = Termination::new("PARENT_EXITED", 143, false);
62
63pub fn watch_parent() -> io::Result<()> {
77 let handler = crate::platform::termination_handler().ok_or_else(|| {
78 io::Error::new(
79 io::ErrorKind::NotFound,
80 "no termination handler is installed",
81 )
82 })?;
83 let Some(parent) = platform::Parent::open()? else {
84 return Ok(());
85 };
86 std::thread::Builder::new()
87 .name("alpha2-parent-watch".to_owned())
88 .spawn(move || {
89 if parent.wait() {
90 handler(PARENT_EXITED);
91 }
92 })?;
93 Ok(())
94}
95
96pub struct KillOnCloseJob {
99 #[cfg(windows)]
100 handle: platform::OwnedHandle,
101}
102
103impl KillOnCloseJob {
104 pub fn new() -> io::Result<Self> {
109 Ok(Self {
110 #[cfg(windows)]
111 handle: platform::kill_on_close_job()?,
112 })
113 }
114
115 pub fn assign(&self, child: &std::process::Child) -> io::Result<()> {
120 #[cfg(windows)]
121 {
122 platform::assign(&self.handle, child)
123 }
124 #[cfg(not(windows))]
125 {
126 let _ = child;
127 Ok(())
128 }
129 }
130}
131
132impl std::fmt::Debug for KillOnCloseJob {
133 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134 formatter
135 .debug_struct("KillOnCloseJob")
136 .finish_non_exhaustive()
137 }
138}
139
140#[cfg(target_os = "linux")]
141mod platform {
142 use std::io;
143
144 pub(super) struct Parent(Option<std::fs::File>);
146
147 impl Parent {
148 pub(super) fn open() -> io::Result<Option<Self>> {
149 use std::os::fd::FromRawFd;
150 let parent = unsafe { libc::getppid() };
152 if parent <= 1 {
153 return Ok(None);
154 }
155 let fd = unsafe { libc::syscall(libc::SYS_pidfd_open, parent, 0) };
157 if fd < 0 {
158 let error = io::Error::last_os_error();
159 return match error.raw_os_error() {
160 Some(libc::ESRCH) => Ok(Some(Self(None))),
161 Some(libc::ENOSYS) => death_signal(parent),
162 _ => Err(error),
163 };
164 }
165 let fd = libc::c_int::try_from(fd)
166 .map_err(|_| io::Error::other("pidfd_open returned an impossible descriptor"))?;
167 let pidfd = unsafe { std::fs::File::from_raw_fd(fd) };
169 if unsafe { libc::getppid() } != parent {
172 return Ok(Some(Self(None)));
173 }
174 Ok(Some(Self(Some(pidfd))))
175 }
176
177 pub(super) fn wait(&self) -> bool {
179 use std::os::fd::AsRawFd;
180 let Some(pidfd) = &self.0 else {
181 return true;
182 };
183 let mut poll = libc::pollfd {
184 fd: pidfd.as_raw_fd(),
185 events: libc::POLLIN,
186 revents: 0,
187 };
188 loop {
189 let ready = unsafe { libc::poll(&raw mut poll, 1, -1) };
191 if ready > 0 {
192 return true;
193 }
194 if ready < 0 && io::Error::last_os_error().kind() != io::ErrorKind::Interrupted {
195 return false;
196 }
197 }
198 }
199 }
200
201 fn death_signal(parent: libc::pid_t) -> io::Result<Option<Parent>> {
203 if unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGTERM) } != 0 {
205 return Err(io::Error::last_os_error());
206 }
207 if unsafe { libc::getppid() } != parent {
210 return Ok(Some(Parent(None)));
211 }
212 Ok(None)
213 }
214}
215
216#[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))]
217mod platform {
218 use std::io;
219
220 pub(super) struct Parent(Option<std::fs::File>);
222
223 impl Parent {
224 pub(super) fn open() -> io::Result<Option<Self>> {
225 use std::os::fd::{AsRawFd, FromRawFd};
226 let parent = unsafe { libc::getppid() };
228 if parent <= 1 {
229 return Ok(None);
230 }
231 let fd = unsafe { libc::kqueue() };
233 if fd < 0 {
234 return Err(io::Error::last_os_error());
235 }
236 let queue = unsafe { std::fs::File::from_raw_fd(fd) };
238 let mut change: libc::kevent = unsafe { std::mem::zeroed() };
240 change.ident = usize::try_from(parent).unwrap_or(0);
241 change.filter = libc::EVFILT_PROC;
242 change.flags = libc::EV_ADD | libc::EV_ONESHOT;
243 change.fflags = libc::NOTE_EXIT;
244 let registered = unsafe {
246 libc::kevent(
247 queue.as_raw_fd(),
248 &raw const change,
249 1,
250 std::ptr::null_mut(),
251 0,
252 std::ptr::null(),
253 )
254 };
255 if registered < 0 {
256 let error = io::Error::last_os_error();
257 return if error.raw_os_error() == Some(libc::ESRCH) {
258 Ok(Some(Self(None)))
259 } else {
260 Err(error)
261 };
262 }
263 if unsafe { libc::getppid() } != parent {
266 return Ok(Some(Self(None)));
267 }
268 Ok(Some(Self(Some(queue))))
269 }
270
271 pub(super) fn wait(&self) -> bool {
273 use std::os::fd::AsRawFd;
274 let Some(queue) = &self.0 else {
275 return true;
276 };
277 loop {
278 let mut event: libc::kevent = unsafe { std::mem::zeroed() };
280 let ready = unsafe {
282 libc::kevent(
283 queue.as_raw_fd(),
284 std::ptr::null(),
285 0,
286 &raw mut event,
287 1,
288 std::ptr::null(),
289 )
290 };
291 if ready > 0 {
292 return true;
293 }
294 if ready < 0 && io::Error::last_os_error().kind() != io::ErrorKind::Interrupted {
295 return false;
296 }
297 }
298 }
299 }
300}
301
302#[cfg(windows)]
303mod platform {
304 use std::io;
305 use std::os::windows::io::AsRawHandle;
306
307 use windows_sys::Win32::Foundation::{
308 CloseHandle, ERROR_INVALID_PARAMETER, FILETIME, HANDLE, INVALID_HANDLE_VALUE, WAIT_OBJECT_0,
309 };
310 use windows_sys::Win32::System::Diagnostics::ToolHelp::{
311 CreateToolhelp32Snapshot, PROCESSENTRY32W, Process32FirstW, Process32NextW,
312 TH32CS_SNAPPROCESS,
313 };
314 use windows_sys::Win32::System::JobObjects::{
315 AssignProcessToJobObject, CreateJobObjectW, JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,
316 JOBOBJECT_EXTENDED_LIMIT_INFORMATION, JobObjectExtendedLimitInformation,
317 SetInformationJobObject,
318 };
319 use windows_sys::Win32::System::Threading::{
320 GetCurrentProcess, GetCurrentProcessId, GetProcessTimes, INFINITE, OpenProcess,
321 PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_SYNCHRONIZE, WaitForSingleObject,
322 };
323
324 pub(super) struct OwnedHandle(HANDLE);
325
326 unsafe impl Send for OwnedHandle {}
328 unsafe impl Sync for OwnedHandle {}
330
331 impl Drop for OwnedHandle {
332 fn drop(&mut self) {
333 unsafe { CloseHandle(self.0) };
335 }
336 }
337
338 pub(super) struct Parent(Option<OwnedHandle>);
340
341 impl Parent {
342 pub(super) fn open() -> io::Result<Option<Self>> {
343 let Some(parent) = parent_id()? else {
344 return Ok(None);
345 };
346 let handle = unsafe {
348 OpenProcess(
349 PROCESS_SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION,
350 0,
351 parent,
352 )
353 };
354 if handle.is_null() {
355 let error = io::Error::last_os_error();
356 return if error.raw_os_error() == i32::try_from(ERROR_INVALID_PARAMETER).ok() {
357 Ok(Some(Self(None)))
358 } else {
359 Err(error)
360 };
361 }
362 let handle = OwnedHandle(handle);
363 let ours = created(unsafe { GetCurrentProcess() })?;
365 if created(handle.0)? > ours {
367 return Ok(Some(Self(None)));
368 }
369 Ok(Some(Self(Some(handle))))
370 }
371
372 pub(super) fn wait(&self) -> bool {
374 let Some(handle) = &self.0 else {
375 return true;
376 };
377 unsafe { WaitForSingleObject(handle.0, INFINITE) == WAIT_OBJECT_0 }
379 }
380 }
381
382 fn parent_id() -> io::Result<Option<u32>> {
383 let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) };
385 if snapshot == INVALID_HANDLE_VALUE {
386 return Err(io::Error::last_os_error());
387 }
388 let snapshot = OwnedHandle(snapshot);
389 let ours = unsafe { GetCurrentProcessId() };
391 let mut entry = PROCESSENTRY32W {
392 dwSize: u32::try_from(std::mem::size_of::<PROCESSENTRY32W>()).unwrap_or(0),
393 ..PROCESSENTRY32W::default()
394 };
395 let mut more = unsafe { Process32FirstW(snapshot.0, &raw mut entry) } != 0;
397 while more {
398 if entry.th32ProcessID == ours {
399 return Ok(Some(entry.th32ParentProcessID).filter(|parent| *parent != 0));
400 }
401 more = unsafe { Process32NextW(snapshot.0, &raw mut entry) } != 0;
403 }
404 Ok(None)
405 }
406
407 fn created(process: HANDLE) -> io::Result<u64> {
409 let zero = || FILETIME {
410 dwLowDateTime: 0,
411 dwHighDateTime: 0,
412 };
413 let (mut created, mut exited, mut kernel, mut user) = (zero(), zero(), zero(), zero());
414 let read = unsafe {
416 GetProcessTimes(
417 process,
418 &raw mut created,
419 &raw mut exited,
420 &raw mut kernel,
421 &raw mut user,
422 )
423 };
424 if read == 0 {
425 return Err(io::Error::last_os_error());
426 }
427 Ok(u64::from(created.dwHighDateTime) << 32 | u64::from(created.dwLowDateTime))
428 }
429
430 pub(super) fn kill_on_close_job() -> io::Result<OwnedHandle> {
431 let job = unsafe { CreateJobObjectW(std::ptr::null(), std::ptr::null()) };
433 if job.is_null() {
434 return Err(io::Error::last_os_error());
435 }
436 let job = OwnedHandle(job);
437 let mut limits = JOBOBJECT_EXTENDED_LIMIT_INFORMATION::default();
438 limits.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
439 let set = unsafe {
441 SetInformationJobObject(
442 job.0,
443 JobObjectExtendedLimitInformation,
444 (&raw const limits).cast(),
445 u32::try_from(std::mem::size_of::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>())
446 .unwrap_or(0),
447 )
448 };
449 if set == 0 {
450 return Err(io::Error::last_os_error());
451 }
452 Ok(job)
453 }
454
455 pub(super) fn assign(job: &OwnedHandle, child: &std::process::Child) -> io::Result<()> {
456 if unsafe { AssignProcessToJobObject(job.0, child.as_raw_handle()) } == 0 {
458 return Err(io::Error::last_os_error());
459 }
460 Ok(())
461 }
462}
463
464#[cfg(not(any(
465 target_os = "linux",
466 target_os = "macos",
467 target_os = "ios",
468 target_os = "freebsd",
469 windows
470)))]
471mod platform {
472 use std::io;
473
474 pub(super) struct Parent;
476
477 impl Parent {
478 pub(super) fn open() -> io::Result<Option<Self>> {
479 Ok(None)
480 }
481
482 pub(super) fn wait(&self) -> bool {
483 false
484 }
485 }
486}
487
488#[cfg(test)]
489#[path = "lifetime_tests.rs"]
490mod tests;