use super::process_handle::ProcessHandle;
use libdd_common::timeout::TimeoutManager;
use crate::shared::configuration::CrashtrackerReceiverConfig;
use core::ptr;
use core::sync::atomic::{AtomicPtr, Ordering::SeqCst};
use libdd_common::unix_utils::{alt_fork, open_file_or_quiet, terminate, PreparedExecve};
use nix::sys::signal::{self, SaFlags, SigAction, SigHandler, SigSet};
use nix::sys::socket;
use std::os::unix::io::{IntoRawFd, RawFd};
#[derive(Debug, thiserror::Error)]
pub enum ReceiverError {
#[error("No receiver path provided")]
NoReceiverPath,
#[error("Failed to connect to receiver: {0}")]
ConnectionError(std::io::Error),
#[error("Failed to create Unix domain socket pair: {0}")]
SocketPairError(nix::Error),
#[error("Failed to create receiver process (fork error code: {0})")]
ForkFailed(i32),
#[error("No receiver config available")]
NoConfig,
#[error("Failed to open file: {0}")]
FileOpenError(std::io::Error),
#[error("Failed to prepare execve: {0}")]
PreparedExecveError(#[from] libdd_common::unix_utils::PreparedExecveError),
}
static RECEIVER_CONFIG: AtomicPtr<(CrashtrackerReceiverConfig, PreparedExecve)> =
AtomicPtr::new(ptr::null_mut());
pub(crate) struct Receiver {
pub handle: ProcessHandle,
}
impl Receiver {
fn from_connector(
unix_socket_path: &str,
connector: fn(&str) -> RawFd,
) -> Result<Self, ReceiverError> {
if unix_socket_path.is_empty() {
return Err(ReceiverError::NoReceiverPath);
}
let uds_fd = connector(unix_socket_path);
if uds_fd < 0 {
return Err(ReceiverError::ConnectionError(
std::io::Error::last_os_error(),
));
}
Ok(Self {
handle: ProcessHandle::new(uds_fd, None),
})
}
pub(crate) fn spawn_from_config(
config: &CrashtrackerReceiverConfig,
prepared_exec: &PreparedExecve,
) -> Result<Self, ReceiverError> {
let stderr = open_file_or_quiet(config.stderr_filename.as_deref())
.map_err(ReceiverError::FileOpenError)?;
let stdout = open_file_or_quiet(config.stdout_filename.as_deref())
.map_err(ReceiverError::FileOpenError)?;
let (uds_parent, uds_child) = socket::socketpair(
socket::AddressFamily::Unix,
socket::SockType::Stream,
None,
socket::SockFlag::empty(),
)
.map_err(ReceiverError::SocketPairError)?;
let (uds_parent, uds_child) = (uds_parent.into_raw_fd(), uds_child.into_raw_fd());
match alt_fork() {
0 => {
let _ = unsafe { libc::close(uds_parent) };
run_receiver_child(prepared_exec, uds_child, stderr, stdout)
}
pid if pid > 0 => {
let _ = unsafe { libc::close(uds_child) };
Ok(Self {
handle: ProcessHandle::new(uds_parent, Some(pid)),
})
}
code => {
Err(ReceiverError::ForkFailed(code))
}
}
}
pub(crate) fn from_crashtracker_config(
config: &crate::shared::configuration::CrashtrackerConfiguration,
) -> Result<Self, ReceiverError> {
let unix_socket_path = config.unix_socket_path().as_deref().unwrap_or_default();
if unix_socket_path.is_empty() {
Self::spawn_from_stored_config()
} else {
Self::from_connector(unix_socket_path, config.unix_socket_connector())
}
}
pub(crate) fn spawn_from_stored_config() -> Result<Self, ReceiverError> {
let receiver_config = RECEIVER_CONFIG.swap(ptr::null_mut(), SeqCst);
if receiver_config.is_null() {
return Err(ReceiverError::NoConfig);
}
let (config, prepared_exec) = unsafe { &*receiver_config };
Self::spawn_from_config(config, prepared_exec)
}
pub fn update_stored_config(config: CrashtrackerReceiverConfig) -> Result<(), ReceiverError> {
let prepared_execve =
PreparedExecve::new(&config.path_to_receiver_binary, &config.args, &config.env)?;
let box_ptr = Box::into_raw(Box::new((config, prepared_execve)));
let old = RECEIVER_CONFIG.swap(box_ptr, SeqCst);
if !old.is_null() {
unsafe {
core::mem::drop(Box::from_raw(old));
}
}
Ok(())
}
pub fn finish(self, timeout_manager: &TimeoutManager) {
self.handle.finish(timeout_manager);
}
}
fn run_receiver_child(
prepared_exec: &PreparedExecve,
uds_child: RawFd,
stderr: RawFd,
stdout: RawFd,
) -> ! {
unsafe {
let _ = libc::dup2(uds_child, 0);
let _ = libc::dup2(stdout, 1);
let _ = libc::dup2(stderr, 2);
}
let _ = unsafe { libc::close(uds_child) };
let _ = unsafe { libc::close(stderr) };
let _ = unsafe { libc::close(stdout) };
let sig_action = SigAction::new(SigHandler::SigDfl, SaFlags::empty(), SigSet::empty());
unsafe {
let _ = signal::sigaction(signal::SIGCHLD, &sig_action);
}
prepared_exec.exec().unwrap_or_else(|_| terminate());
terminate();
}