libdd-crashtracker 2.0.1

Detects program crashes and reports them to datadog backend.
Documentation
// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/
// SPDX-License-Identifier: Apache-2.0

use libdd_common::timeout::TimeoutManager;
use libdd_common::unix_utils::{reap_child_non_blocking, wait_for_pollhup};
use nix::errno::Errno;
use nix::unistd::Pid;
use std::os::unix::io::RawFd;

pub(crate) struct ProcessHandle {
    pub uds_fd: RawFd,
    pub pid: Option<i32>,
}

impl ProcessHandle {
    pub fn new(uds_fd: RawFd, pid: Option<i32>) -> Self {
        Self { uds_fd, pid }
    }

    pub fn finish(&self, timeout_manager: &TimeoutManager) {
        let result = wait_for_pollhup(self.uds_fd, timeout_manager);
        debug_assert_eq!(result, Ok(true), "wait_for_pollhup failed: {result:?}");

        if let Some(pid) = self.pid {
            // If we have less than the minimum amount of time, give ourselves a few scheduler
            // slices worth of headroom to help guarantee that we don't leak a zombie process.
            let kill_result = unsafe { libc::kill(pid, libc::SIGKILL) };
            if kill_result != 0 {
                let errno = Errno::last_raw();
                if errno == libc::ESRCH {
                    // Child has already exited, which is fine; no action needed
                } else {
                    eprintln!("Warning: kill({pid}, SIGKILL) failed with errno {errno}");
                    debug_assert_eq!(
                        errno,
                        libc::ESRCH,
                        "kill failed with result: {kill_result}, errno: {errno}"
                    );
                }
            }

            // `self` is actually a handle to a child process and `self.pid` is the child process's
            // pid.
            let child_pid = Pid::from_raw(pid);
            let result = reap_child_non_blocking(child_pid, timeout_manager);
            // Ok(true):  child was successfully reaped
            // Ok(false): child was already reaped (ECHILD) -> no zombie risk
            //            This can happen because we defer SIGCHLD during crash handling
            debug_assert!(
                matches!(result, Ok(true) | Ok(false)),
                "reap_child_non_blocking failed: {result:?}"
            );
        }
    }
}