mod builder;
pub use builder::CrashtrackerConfigurationBuilder;
use core::time::Duration;
use libdd_common::Endpoint;
use serde::{Deserialize, Serialize};
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum StacktraceCollection {
#[default]
Disabled,
WithoutSymbols,
EnabledWithInprocessSymbols,
EnabledWithSymbolsInReceiver,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CrashtrackerConfiguration {
additional_files: Vec<String>,
#[serde(default)]
collect_all_threads: bool,
create_alt_stack: bool,
demangle_names: bool,
endpoint: Option<Endpoint>,
#[serde(default = "default_max_threads")]
max_threads: usize,
resolve_frames: StacktraceCollection,
signals: Vec<i32>,
timeout: Duration,
unix_socket_path: Option<String>,
#[serde(skip, default = "default_unix_socket_connector_value")]
unix_socket_connector: fn(&str) -> std::os::fd::RawFd,
use_alt_stack: bool,
}
impl PartialEq for CrashtrackerConfiguration {
fn eq(&self, other: &Self) -> bool {
self.additional_files == other.additional_files
&& self.collect_all_threads == other.collect_all_threads
&& self.create_alt_stack == other.create_alt_stack
&& self.demangle_names == other.demangle_names
&& self.endpoint == other.endpoint
&& self.max_threads == other.max_threads
&& self.resolve_frames == other.resolve_frames
&& self.signals == other.signals
&& self.timeout == other.timeout
&& self.unix_socket_path == other.unix_socket_path
&& self.use_alt_stack == other.use_alt_stack
}
}
pub const fn default_max_threads() -> usize {
256
}
pub fn default_unix_socket_connector(unix_socket_path: &str) -> std::os::fd::RawFd {
use std::os::fd::IntoRawFd;
use std::os::unix::net::UnixStream;
#[cfg(target_os = "linux")]
let stream = if unix_socket_path.starts_with(['.', '/']) {
UnixStream::connect(unix_socket_path)
} else {
use std::os::linux::net::SocketAddrExt;
match std::os::unix::net::SocketAddr::from_abstract_name(unix_socket_path) {
Ok(addr) => UnixStream::connect_addr(&addr),
Err(e) => Err(e),
}
};
#[cfg(not(target_os = "linux"))]
let stream = UnixStream::connect(unix_socket_path);
match stream {
Ok(s) => s.into_raw_fd(),
Err(_) => -1,
}
}
fn default_unix_socket_connector_value() -> fn(&str) -> std::os::fd::RawFd {
default_unix_socket_connector
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
pub struct CrashtrackerReceiverConfig {
pub args: Vec<String>,
pub env: Vec<(String, String)>,
pub path_to_receiver_binary: String,
pub stderr_filename: Option<String>,
pub stdout_filename: Option<String>,
}
impl CrashtrackerReceiverConfig {
pub fn new(
args: Vec<String>,
env: Vec<(String, String)>,
path_to_receiver_binary: String,
stderr_filename: Option<String>,
stdout_filename: Option<String>,
) -> anyhow::Result<Self> {
anyhow::ensure!(
stderr_filename.is_none() && stdout_filename.is_none()
|| stderr_filename != stdout_filename,
"Can't give the same filename for stderr ({stderr_filename:?})
and stdout ({stdout_filename:?}), they will conflict with each other"
);
Ok(Self {
args,
env,
path_to_receiver_binary,
stderr_filename,
stdout_filename,
})
}
}
impl CrashtrackerConfiguration {
pub fn builder() -> CrashtrackerConfigurationBuilder {
CrashtrackerConfigurationBuilder::default()
}
pub fn additional_files(&self) -> &Vec<String> {
&self.additional_files
}
pub fn collect_all_threads(&self) -> bool {
self.collect_all_threads
}
pub fn create_alt_stack(&self) -> bool {
self.create_alt_stack
}
pub fn max_threads(&self) -> usize {
self.max_threads
}
pub fn use_alt_stack(&self) -> bool {
self.use_alt_stack
}
pub(crate) fn endpoint(&self) -> &Option<Endpoint> {
&self.endpoint
}
pub fn resolve_frames(&self) -> StacktraceCollection {
self.resolve_frames
}
pub fn signals(&self) -> &Vec<i32> {
&self.signals
}
pub fn timeout(&self) -> Duration {
self.timeout
}
pub fn unix_socket_path(&self) -> &Option<String> {
&self.unix_socket_path
}
pub fn unix_socket_connector(&self) -> fn(&str) -> std::os::fd::RawFd {
self.unix_socket_connector
}
pub fn demangle_names(&self) -> bool {
self.demangle_names
}
pub fn set_collect_all_threads(&mut self, collect: bool) {
self.collect_all_threads = collect;
}
pub fn set_max_threads(&mut self, max: usize) {
self.max_threads = max;
}
pub fn set_create_alt_stack(&mut self, create_alt_stack: bool) -> anyhow::Result<()> {
anyhow::ensure!(
!create_alt_stack || self.use_alt_stack,
"Cannot create an altstack without using it"
);
self.create_alt_stack = create_alt_stack;
Ok(())
}
pub fn set_use_alt_stack(&mut self, use_alt_stack: bool) -> anyhow::Result<()> {
anyhow::ensure!(
!self.create_alt_stack || use_alt_stack,
"Cannot create an altstack without using it"
);
self.use_alt_stack = use_alt_stack;
Ok(())
}
pub fn set_unix_socket_path(&mut self, path: String) {
self.unix_socket_path = Some(path);
}
pub fn set_unix_socket_connector(&mut self, connector: fn(&str) -> std::os::fd::RawFd) {
self.unix_socket_connector = connector;
}
}
#[cfg(test)]
mod tests {
use super::CrashtrackerReceiverConfig;
#[test]
fn test_receiver_config_new() -> anyhow::Result<()> {
let args = vec!["foo".to_string()];
let env = vec![
("bar".to_string(), "baz".to_string()),
("apple".to_string(), "banana".to_string()),
];
let path_to_receiver_binary = "/tmp/crashtracker-receiver-binary".to_string();
let stderr_filename = None;
let stdout_filename = None;
let config = CrashtrackerReceiverConfig::new(
args.clone(),
env.clone(),
path_to_receiver_binary.clone(),
stderr_filename.clone(),
stdout_filename.clone(),
)?;
assert_eq!(config.args, args);
assert_eq!(config.env, env);
assert_eq!(config.path_to_receiver_binary, path_to_receiver_binary);
assert_eq!(config.stderr_filename, stderr_filename);
assert_eq!(config.stdout_filename, stdout_filename);
let stderr_filename = None;
let stdout_filename = Some("/tmp/stdout.txt".to_string());
let config = CrashtrackerReceiverConfig::new(
args.clone(),
env.clone(),
path_to_receiver_binary.clone(),
stderr_filename.clone(),
stdout_filename.clone(),
)?;
assert_eq!(config.args, args);
assert_eq!(config.env, env);
assert_eq!(config.path_to_receiver_binary, path_to_receiver_binary);
assert_eq!(config.stderr_filename, stderr_filename);
assert_eq!(config.stdout_filename, stdout_filename);
let stderr_filename = Some("/tmp/stderr.txt".to_string());
let stdout_filename = None;
let config = CrashtrackerReceiverConfig::new(
args.clone(),
env.clone(),
path_to_receiver_binary.clone(),
stderr_filename.clone(),
stdout_filename.clone(),
)?;
assert_eq!(config.args, args);
assert_eq!(config.env, env);
assert_eq!(config.path_to_receiver_binary, path_to_receiver_binary);
assert_eq!(config.stderr_filename, stderr_filename);
assert_eq!(config.stdout_filename, stdout_filename);
let stderr_filename = Some("/tmp/stderr.txt".to_string());
let stdout_filename = Some("/tmp/stdout.txt".to_string());
let config = CrashtrackerReceiverConfig::new(
args.clone(),
env.clone(),
path_to_receiver_binary.clone(),
stderr_filename.clone(),
stdout_filename.clone(),
)?;
assert_eq!(config.args, args);
assert_eq!(config.env, env);
assert_eq!(config.path_to_receiver_binary, path_to_receiver_binary);
assert_eq!(config.stderr_filename, stderr_filename);
assert_eq!(config.stdout_filename, stdout_filename);
let stderr_filename = Some("/tmp/shared.txt".to_string());
let stdout_filename = Some("/tmp/shared.txt".to_string());
CrashtrackerReceiverConfig::new(
args.clone(),
env.clone(),
path_to_receiver_binary.clone(),
stderr_filename.clone(),
stdout_filename.clone(),
)
.unwrap_err();
Ok(())
}
}