use super::process_handle::ProcessHandle;
use super::receiver_manager::Receiver;
use libdd_common::timeout::TimeoutManager;
use super::emitters::{emit_crashreport, CrashKindData};
use crate::shared::configuration::CrashtrackerConfiguration;
use libc::{siginfo_t, ucontext_t};
use libdd_common::unix_utils::{alt_fork, terminate};
use nix::sys::signal::{self, SaFlags, SigAction, SigHandler, SigSet};
use std::os::unix::io::RawFd;
use std::os::unix::{io::FromRawFd, net::UnixStream};
use thiserror::Error;
pub(crate) struct Collector {
pub handle: ProcessHandle,
}
#[derive(Debug, Error)]
pub enum CollectorSpawnError {
#[error("Failed to fork collector process (error code: {0})")]
ForkFailed(i32),
}
impl Collector {
pub(crate) fn spawn(
receiver: &Receiver,
config: &CrashtrackerConfiguration,
config_str: &str,
metadata_str: &str,
message: Option<&str>,
sig_info: *const siginfo_t,
ucontext: *const ucontext_t,
) -> Result<Self, CollectorSpawnError> {
let pid = unsafe { libc::getpid() };
let tid = current_tid();
let fork_result = alt_fork();
match fork_result {
0 => {
run_collector_child(
config,
config_str,
metadata_str,
message,
sig_info,
ucontext,
receiver.handle.uds_fd,
pid,
tid,
);
}
pid if pid > 0 => Ok(Self {
handle: ProcessHandle::new(receiver.handle.uds_fd, Some(pid)),
}),
code => {
Err(CollectorSpawnError::ForkFailed(code))
}
}
}
pub fn finish(self, timeout_manager: &TimeoutManager) {
self.handle.finish(timeout_manager);
}
}
#[cfg(target_os = "linux")]
fn current_tid() -> libc::pid_t {
unsafe { libc::syscall(libc::SYS_gettid) as libc::pid_t }
}
#[cfg(not(target_os = "linux"))]
fn current_tid() -> libc::pid_t {
0
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn run_collector_child(
config: &CrashtrackerConfiguration,
config_str: &str,
metadata_str: &str,
message: Option<&str>,
sig_info: *const siginfo_t,
ucontext: *const ucontext_t,
uds_fd: RawFd,
ppid: libc::pid_t,
crashing_tid: libc::pid_t,
) -> ! {
let _ = unsafe { libc::close(0) };
let _ = unsafe { libc::close(1) };
let _ = unsafe { libc::close(2) };
let _ = unsafe {
signal::sigaction(
signal::SIGPIPE,
&SigAction::new(SigHandler::SigIgn, SaFlags::empty(), SigSet::empty()),
)
};
let mut unix_stream = unsafe { UnixStream::from_raw_fd(uds_fd) };
let report = emit_crashreport(
&mut unix_stream,
config,
config_str,
metadata_str,
message,
CrashKindData::UnixSignal { sig_info, ucontext },
ppid,
crashing_tid,
);
if let Err(e) = report {
eprintln!("Failed to flush crash report: {e}");
terminate();
}
unsafe { libc::_exit(0) };
}